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

Gar*_*ill 23 c# memory-leaks restsharp async-await c#-5.0

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

RestSharp的RestClient类有一个ExecuteAsync方法,它接受一个回调参数,我希望能够将一个包装器放在允许我进行await整个操作的包装器上.该ExecuteAsync方法看起来像这样:

public RestRequestAsyncHandle ExecuteAsync(IRestRequest request, Action<IRestResponse> callback);
Run Code Online (Sandbox Code Playgroud)

我以为我一切都很好.我曾经TaskCompletionSource把这个ExecuteAsync电话包裹在我可以等待的东西中,如下所示:

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;
}
Run Code Online (Sandbox Code Playgroud)

这对我的大多数应用程序都很好.

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

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

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

根据分析器,这些RestResponse对象被保持活着,因为它们被TaskCompletionSource(通过Task)引用,而这些对象又被保持活着,因为它是CancellationTokenSource通过其注册的回调列表引用的.

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

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

svi*_*ick 26

我无法取消注册回调

实际上,你可以.返回值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;
    }
}
Run Code Online (Sandbox Code Playgroud)


MNG*_*inn 6

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