为什么这个可取消的任务会导致内存泄漏?

huangapple go评论72阅读模式
英文:

Why does this cancellable Task result in a memory leak?

问题

这个任务扩展会导致内存泄漏的原因是,在AsCancellable方法中,它使用了TaskCompletionSource<T>(缩写为TCS),而TCS 对于未完成的任务引用保留了对任务的引用,这可能会导致内存泄漏。在这种情况下,TCS 可能会保留对ConsoleHandler方法创建的Task<string>对象的引用,因为在注册取消回调和ContinueWith回调时,TCS 可能会保留对这些任务的引用。

为了解决内存泄漏问题,你可以尝试手动解除 TCS 的引用。可以在 AsCancellable 方法的最后,返回 tcs.Task 之后,将 tcs 设置为 null,以确保不再保留对 TCS 的引用。这将允许.NET 垃圾收集器在不再需要时正确回收相关的对象。

以下是修改后的代码示例:

public static class TaskExtensions
{
    public static Task<T> AsCancellable<T>(this Task<T> task, CancellationToken token)
    {
        if (!token.CanBeCanceled)
        {
            return task;
        }

        var tcs = new TaskCompletionSource<T>();
        // This cancels the returned task:
        // 1. If the token has been canceled, it cancels the TCS straightaway
        // 2. Otherwise, it attempts to cancel the TCS whenever
        //    the token indicates cancelled
        token.Register(() => tcs.TrySetCanceled(token),
            useSynchronizationContext: false);

        task.ContinueWith(t =>
        {
            // Complete the TCS per task status
            // If the TCS has been cancelled, this continuation does nothing
            if (task.IsCanceled)
            {
                tcs.TrySetCanceled();
            }
            else if (task.IsFaulted)
            {
                tcs.TrySetException(t.Exception!);
            }
            else
            {
                tcs.TrySetResult(t.Result);
            }
        },
            CancellationToken.None,
            TaskContinuationOptions.ExecuteSynchronously,
            TaskScheduler.Default);

        var resultTask = tcs.Task;
        tcs = null; // Release the reference to TCS
        return resultTask;
    }
}

这个修改应该能够解决内存泄漏问题。希望这对你有所帮助。

英文:

I have a program where part of it reads from the console for input from another program.

So I have a Task that looks like this:

public async Task ConsoleHandler()
{
    while (true)
    {
        try
        {
            string command = (await Console.In.ReadLineAsync().AsCancellable(
                _cancellationTokenSource.Token))!;

            //Process the command
        }
        catch (Exception ex)
        {
            //Do something to report then wait for next command
        }
    }
}

Normally Console.ReadLineAsync is not a cancel-able task, so I used the code from this question to create the following task extension:

public static class TaskExtensions
{
    public static Task&lt;T&gt; AsCancellable&lt;T&gt;(this Task&lt;T&gt; task,
        CancellationToken token)
    {
        if (!token.CanBeCanceled)
        {
            return task;
        }

        var tcs = new TaskCompletionSource&lt;T&gt;();
        // This cancels the returned task:
        // 1. If the token has been canceled, it cancels the TCS straightaway
        // 2. Otherwise, it attempts to cancel the TCS whenever
        //    the token indicates cancelled
        token.Register(() =&gt; tcs.TrySetCanceled(token),
            useSynchronizationContext: false);

        task.ContinueWith(t =&gt;
        {
            // Complete the TCS per task status
            // If the TCS has been cancelled, this continuation does nothing
            if (task.IsCanceled)
            {
                tcs.TrySetCanceled();
            }
            else if (task.IsFaulted)
            {
                tcs.TrySetException(t.Exception!);
            }
            else
            {
                tcs.TrySetResult(t.Result);
            }
        },
            CancellationToken.None,
            TaskContinuationOptions.ExecuteSynchronously,
            TaskScheduler.Default);

        return tcs.Task;
    }
}

The problem, when running this in .NET Core 6.0.10, is that memory grows until I run out of memory (this is running on an embedded Linux device). Removing the call to .AsCancellable(...) removes the memory leak. This happens even if there is no input on the Console.

Why does this task extension cause a memory leak?

答案1

得分: 3

"AsCancellable" 没有正确实现。它在 "token" 上注册了一个回调,但从不释放这个注册。因此,注册信息堆积在 "CancellationToken" 的内部结构中。

由于您正在使用.NET 6,您无需使用 "AsCancellable" 方法。您可以改为使用正确实现的 WaitAsync API。

英文:

The AsCancellable is not implemented correctly. It registers a callback on the token, and never disposes the registration. So the registrations are piling up in the internal structures of the CancellationToken.

Since you are using .NET 6, you have no need for the AsCancellable method. You can use the WaitAsync API instead, which is, of course, correctly implemented.

huangapple
  • 本文由 发表于 2023年2月24日 01:39:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/75548399.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定