为什么我的异步/等待与CancellationTokenSource泄漏内存?

时间:2013-01-31 13:44:43

标签: c# memory-leaks async-await restsharp c#-5.0

我有一个.NET(C#)应用程序,它广泛使用async / await。我觉得我已经掌握了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进行数百次调用,该操作显示带有取消按钮的进度对话框。您会在上面的代码中看到我将CancellationToken传递给ExecuteRequestAsync;对于此长时间运行的操作,令牌与对象的CancellationTokenSource“属于”关联,如果用户单击取消按钮,则调用其Cancel方法。到目前为止一切顺利(取消按钮确实有效)。

我的问题是,在长时间运行的应用程序中,我的应用程序的内存使用量会在操作完成之前耗尽内存。

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

根据探查器,这些RestResponse个对象被保持活着,因为它们被TaskCompletionSource(通过Task)引用,而CancellationTokenSource又被保留了,因为它是通过其注册的回调列表从{{1}}引用的。

通过这一切,我收集到为每个请求注册取消回调意味着与所有这些请求相关联的整个对象图将一直存在,直到整个操作完成。难怪它耗尽内存: - )

所以我猜我的问题不是“为什么会泄漏”,而是“如何阻止它”。我不能取消 - 注册回调,那么可以我做什么?

2 个答案:

答案 0 :(得分:24)

  

我无法取消注册回调

实际上,你可以。 The return value of Register() is

  

可用于取消注册回调的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;
    }
}

答案 1 :(得分:6)

您需要保持Register()调用返回的CancellationTokenRegistration。处置CancellationTokenRegistration取消注册回调。