.NET 中的内存泄漏在执行异步超过同步时

Memory leaks in .NET when doing async over sync

我有一种情况,我必须同步调用一个异步方法,它是这样完成的:

 obj.asyncMethod().Wait(myCancelToken)

如果取消标记被切换,尽管通过 using 语句激活,任务中的一次性标记将不会被释放。

下面的程序说明了这个问题:

        using System;
        using System.Threading;
        using System.Threading.Tasks;

        namespace LeakTest {

            class Program {
                static void Main(string[] args) {
                    try {
                        var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
                        LongRunningTask().Wait(timeout.Token);
                    } catch (OperationCanceledException error) {                
                        //  handling timeout is logically okay, but expect nothing to be leaked
                    }
                    Console.WriteLine("Leaked Instances = {0}", DisposableResource.Instances);
                    Console.ReadKey();
                }

                static async Task LongRunningTask() {
                    using (var resource = new DisposableResource()) {
                        await Task.Run( () => Thread.Sleep(1000));
                    }
                }

                public class DisposableResource : IDisposable {
                    public static int Instances = 0;
                    public DisposableResource() {
                        Instances++;
                    }

                    public void Dispose() {
                        Instances--;
                    }
                }
            }
        }

似乎 Wait 方法只是在取消时杀死任务线程,而不是在该线程内触发异常并让它自然终止。问题是为什么?

您取消了 Wait(timeout.Token) 返回的任务,而不是 LongRunningTask 返回的任务,如果您想取消那个任务,请将令牌传递给 Task.Run 并使用 await Task.Delay 而不是 Thread.Sleep 并将令牌也传递到那里。

static void Main(string[] args)
{
    try
    {
        var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
        LongRunningTask(timeout.Token).Wait();
    }
    catch (AggregateException error)
    {
        //  handling timeout is logically okay, but expect nothing to be leaked
    }
    Console.WriteLine("Leaked Instances = {0}", DisposableResource.Instances);
    Console.ReadLine();
}

static async Task LongRunningTask(CancellationToken token)
{

    using (var resource = new DisposableResource())
    {
        await Task.Run(async () => await Task.Delay(1000, token), token);
    }
}

public class DisposableResource : IDisposable
{
    public static int Instances = 0;
    public DisposableResource()
    {
        Instances++;
    }

    public void Dispose()
    {
        Instances--;
    }
}

注意 一旦长 运行 操作完成,using 语句仍将处理资源。 运行 这个例子:

static void Main(string[] args)
{
    try {
            var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
            LongRunningTask().Wait(timeout.Token);
        } catch (OperationCanceledException error) {                
            //  handling timeout is logically okay, but expect nothing to be leaked
        }

    Console.WriteLine("Leaked Instances = {0}", DisposableResource.Instances);

    Console.ReadKey();
}

static async Task LongRunningTask()
{
    using (var resource = new DisposableResource())
    {
        await Task.Run(() => Thread.Sleep(1000));
    }
}

public class DisposableResource : IDisposable
{
    public static int Instances = 0;
    public DisposableResource()
    {
        Instances++;
    }

    public void Dispose()
    {
        Instances--;
        Console.WriteLine("Disposed resource. Leaked Instances = {0}", Instances);
    }
}

输出
泄漏实例 = 1
处置的资源。泄漏实例 = 0

It seems Wait method just kills the task thread on cancellation instead of triggering an exception within that thread

你错了,当你取消时,唯一发生的事情就是你停止等待 Wait(myCancelToken) 完成,任务仍在后台 运行。

为了取消后台任务,您必须将取消令牌传递到链下的所有方法中。如果您希望最内层(长 运行 层)尽早停止,该代码必须在其整个代码中调用 token.ThrowIfCancellationRequested()