为什么我的异步/等待与取消令牌源泄漏内存

本文关键字:令牌 泄漏 内存 取消 我的 异步 等待 为什么 | 更新日期: 2023-09-27 17:57:02

我有一个广泛使用async/await的.NET (C#)应用程序。我觉得我已经掌握了 async/await,但我正在尝试使用一个库 (RestSharp),该库具有较旧的(或者我应该说不同)的编程模型,该模型使用异步操作的回调。

RestSharp 的 RestClient 类有一个接受回调参数的 ExecuteAsync 方法,我希望能够在它周围放置一个包装器,以便我await整个操作。ExecuteAsync方法如下所示:

public RestRequestAsyncHandle ExecuteAsync(IRestRequest request, Action<IRestResponse> callback);

以为我一切都很好。我用TaskCompletionSourceExecuteAsync调用包装在我可以等待的东西中,如下所示:

public static async Task<T> ExecuteRequestAsync<T>(RestRequest request, CancellationToken cancellationToken) where T : new()
{
    var response = await ExecuteTaskAsync(request, cancellationToken);
    cancellationToken.ThrowIfCancellationRequested();
    return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(response.Content);
}
private static async Task<IRestResponse> ExecuteTaskAsync(RestRequest request, CancellationToken cancellationToken)
{
    var taskCompletionSource = new TaskCompletionSource<IRestResponse>();
    var asyncHandle = _restClient.ExecuteAsync(request, r => 
    {
        taskCompletionSource.SetResult(r); 
    });
    cancellationToken.Register(() => asyncHandle.Abort());
    return await taskCompletionSource.Task;
}

这对于我的大部分应用程序来说都运行良好。

但是,我有一部分应用程序作为单个操作的一部分对我的ExecuteRequestAsync执行数百次调用,并且该操作显示带有取消按钮的进度对话框。您会在上面的代码中看到,我正在向ExecuteRequestAsync传递一个CancellationToken;对于此长时间运行的操作,令牌与"属于"对话框的CancellationTokenSource相关联,如果使用单击"取消"按钮,则会调用其Cancel方法。到目前为止一切顺利(取消按钮确实有效)。

我的问题是,在

长时间运行的应用程序期间,我的应用程序的内存使用量激增,以至于在操作完成之前内存不足。

我在它上运行了一个内存分析器,发现即使在垃圾回收之后,我仍然有很多RestResponse对象仍在内存中。(他们反过来拥有大量数据,因为我通过网络发送数兆字节的文件)。

根据探查器的说法,这些RestResponse对象之所以保持活动状态,是因为它们被TaskCompletionSource引用(通过Task),而又保持活动状态,因为它是通过其注册的回调列表从CancellationTokenSource引用的

从所有这些中,我发现为每个请求注册取消回调意味着与所有这些请求关联的整个对象图将一直存在,直到整个操作完成。难怪内存不足:-)

所以我想我的问题不是"为什么它会泄漏",而是"我如何阻止它"。我无法取消注册回调,该怎么办?

为什么我的异步/等待与取消令牌源泄漏内存

我无法取消注册回调

实际上,你可以。Register()的返回值为:

可用于取消注册回调的CancellationTokenRegistration实例。

要实际取消注册回调,请对返回的值调用Dispose()

在您的情况下,您可以这样做:

private static async Task<IRestResponse> ExecuteTaskAsync(
    RestRequest request, CancellationToken cancellationToken)
{
    var taskCompletionSource = new TaskCompletionSource<IRestResponse>();
    var asyncHandle = _restClient.ExecuteAsync(
        request, r => taskCompletionSource.SetResult(r));
    using (cancellationToken.Register(() => asyncHandle.Abort()))
    {
        return await taskCompletionSource.Task;
    }
}

你需要保留 Register() 调用返回的 CancelTokenRegistration。释放取消令牌注册取消注册回调。