我有兴趣将系统资源与任务并行库和延续结合使用。
请考虑以下使用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);
此配置存在多个问题,我想知道如何最好地处理这个问题。到目前为止我发现的主要项目是:
responseTask
核心的执行通过在异步执行期间不阻塞用户线程来有效地使用资源。但是,由于continuation被定义为Func
lambda,执行continuation的线程将在return
行上阻塞,直到后续请求的执行完成。更好的情况是在不阻塞用户线程的同时提供类似继续的行为。
responseTask
和finalResultTask
的行为在取消方面有所不同。如果在responseTask
执行期间取消操作,responseTask
将进入状态TaskStatus.Canceled
。但是,如果在finalResultTask
执行期间取消操作,则尝试访问Result
属性将导致异常,从而导致任务进入TaskStatus.Failed
状态。
AggregateException
属性时抛出Result
,那么finalResultTask
可能会有一个双重包装的真正内部异常(我不确定是否特殊案例发生在这里),InnerException
的{{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);