27

我有一个广泛使用 async/await 的 .NET (C#) 应用程序。我觉得我已经对异步/等待有所了解,但我正在尝试使用一个库(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,并且该操作显示了一个带有取消按钮的进度对话框。您会在上面的代码中看到我将 a 传递CancellationTokenExecuteRequestAsync; 对于这个长时间运行的操作,令牌与CancellationTokenSource对话框的“属于”相关联,Cancel如果用户单击取消按钮,则调用其方法。到目前为止一切顺利(取消按钮确实有效)。

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

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

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

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

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

4

2 回答 2

29

我无法取消注册回调

事实上,你可以。的返回值为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;
    }
}
于 2013-01-31T15:34:37.880 回答
8

您需要保留 Register() 调用返回的 CancellationTokenRegistration。处理 CancellationTokenRegistration 会取消注册回调。

于 2013-01-31T15:34:46.553 回答