TPL延续中的高效资源使用

时间:2013-07-09 13:09:39

标签: c# asynchronous task-parallel-library continuations

我有兴趣将系统资源与任务并行库和延续结合使用。

请考虑以下使用GetResponseAsync() extension method defined in another recent question

的方案
WebRequest request = HttpWebRequest.Create(uri);
Task<WebResponse> responseTask = request.GetResponseAsync(cancellationToken);
Func<Task<WebResponse>, WebResponse> continuation =
    task =>
    {
        WebRequest followup = HttpWebRequest.Create(uri2);
        return followup.GetResponseAsync(cancellationToken).Result;
    };
Task<WebResponse> finalResultTask = responseTask.ContinueWith(continuation, cancellationToken);

此配置存在多个问题,我想知道如何最好地处理这个问题。到目前为止我发现的主要项目是:

  1. responseTask核心的执行通过在异步执行期间不阻塞用户线程来有效地使用资源。但是,由于continuation被定义为Func lambda,执行continuation的线程将在return行上阻塞,直到后续请求的执行完成。更好的情况是在不阻塞用户线程的同时提供类似继续的行为。

  2. responseTaskfinalResultTask的行为在取消方面有所不同。如果在responseTask执行期间取消操作,responseTask将进入状态TaskStatus.Canceled。但是,如果在finalResultTask执行期间取消操作,则尝试访问Result属性将导致异常,从而导致任务进入TaskStatus.Failed状态。

    • 在失败方面,行为也可能不同。如果在从continuation返回时尝试访问AggregateException属性时抛出Result,那么finalResultTask可能会有一个双重包装的真正内部异常(我不确定是否特殊案例发生在这里),InnerException的{​​{1}}属性可以在第一个任务失败时提供对实际异常的直接访问。

1 个答案:

答案 0 :(得分:3)

如果您使用Unwrap扩展方法,我怀疑您的问题都会得到解决。您的目标是通过延续功能返回新任务;但是,由于继续本身 作为任务执行,这将导致额外的嵌套级别(任务返回任务)。因此,您需要使用Unwrap来消除此嵌套,这将为您提供绑定到内部任务结果的代理任务。

WebRequest request = HttpWebRequest.Create(uri);
Task<WebResponse> responseTask = request.GetResponseAsync(cancellationToken);
Func<Task<WebResponse>, Task<WebResponse> continuation =
    task =>
    {
        WebRequest followup = HttpWebRequest.Create(uri2);
        return followup.GetResponseAsync(cancellationToken);
    };

Task<Task<WebResponse>> finalResultTask = responseTask.ContinueWith(continuation);
Task<WebResponse> proxyTask = finalResultTask.Unwrap();

快速优化:由于您的延续功能除了通过GetResponseAsync生成新任务之外几乎没有其他功能,您可以通过将其指定为ExecuteSynchronously来减少执行开销:

Task<Task<WebResponse>> finalResultTask = responseTask.ContinueWith(continuation, 
    TaskContinuationOptions.ExecuteSynchronously);

修改:根据Per Servy的建议,您也应该将CancellationToken传递给续续功能:

Task<Task<WebResponse>> finalResultTask = responseTask.ContinueWith(continuation, 
    cancellationToken,
    TaskContinuationOptions.ExecuteSynchronously,
    TaskScheduler.Current);