我有一个.NET(C#)应用程序,该应用程序广泛使用async/await。我感觉自己已经开始异步/等待,但是我正在尝试使用一个库(RestSharp),该库具有较旧的(或者也许我应该说不同的)编程模型,该模型使用回调进行异步操作。

RestSharp的RestClient类具有一个带有回调参数的ExecuteAsync方法,我希望能够对它进行包装,从而使我能够对整个操作进行awaitExecuteAsync方法看起来像这样:

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;对于此长时间运行的操作, token 与对话框的“属于”对话框的CancellationTokenSource关联,如果用户单击“取消”按钮,则会调用该对话框的Cancel方法。到目前为止,一切都很好(“取消”按钮确实起作用了)。

我的问题是,在长时间运行的应用程序期间,我的应用程序的内存使用率急剧上升,以至于在操作完成之前它耗尽了内存。

我在上面运行了一个内存探查器,发现即使在垃圾回收之后,我的内存中仍有许多RestResponse对象。 (它们又具有大量数据,因为我通过网络发送了数兆字节的文件)。

根据探查器,那些RestResponse对象被保持 Activity 状态,因为它们由TaskCompletionSource引用(通过Task),而CancellationTokenSource对象又被保持 Activity 状态,因为它是通过已注册的回调列表从ojit_code引用的。

从所有这些中,我收集到为每个请求注册取消回调都意味着与所有这些请求关联的对象的整个图将继续存在,直到完成整个操作为止。难怪它用完了内存:-)

因此,我想我的问题不是“它为什么泄漏”,而是“我如何停止它”。我无法取消注册回调,该怎么办?

最佳答案



其实可以。 The return value of Register() is:



要实际注销回调,请在返回值上调用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;
    }
}

关于c# - 为什么我的带有CancellationTokenSource的异步/等待泄漏内存?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/14627226/

10-17 01:54