如果您不想在方法内部使用async / await,但仍然要“装饰”它以便能够从外部使用TaskCompletionSource.cs的await关键字:
public static Task<T> RunAsync<T>(Func<T> function)
{
if (function == null) throw new ArgumentNullException(“function”);
var tcs = new TaskCompletionSource<T>();
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
T result = function();
tcs.SetResult(result);
}
catch(Exception exc) { tcs.SetException(exc); }
});
return tcs.Task;
}
从这里到这里
为了用Tasks支持这种范例,我们需要一种方法来保留Task的外观以及将任意异步操作称为Task的能力,但是要根据提供服务的底层基础结构的规则来控制Task的生存期。异步,并且这样做的方式不会花费太多。这是TaskCompletionSource的目的。
我看到也在.NET源代码中使用过。WebClient.cs:
[HostProtection(ExternalThreading = true)]
[ComVisible(false)]
public Task<string> UploadStringTaskAsync(Uri address, string method, string data)
{
// Create the task to be returned
var tcs = new TaskCompletionSource<string>(address);
// Setup the callback event handler
UploadStringCompletedEventHandler handler = null;
handler = (sender, e) => HandleCompletion(tcs, e, (args) => args.Result, handler, (webClient, completion) => webClient.UploadStringCompleted -= completion);
this.UploadStringCompleted += handler;
// Start the async operation.
try { this.UploadStringAsync(address, method, data, tcs); }
catch
{
this.UploadStringCompleted -= handler;
throw;
}
// Return the task that represents the async operation
return tcs.Task;
}
最后,我发现以下内容也很有用:
我一直被问到这个问题。这意味着在某个地方必须有一些线程阻止对外部资源的I / O调用。因此,异步代码释放了请求线程,但仅以牺牲系统中其他线程的代价为代价,对吗?一点都不。为了理解异步请求为什么会扩展,我将跟踪一个异步I / O调用的(简化)示例。假设一个请求需要写入文件。请求线程调用异步写入方法。WriteAsync由基类库(BCL)实现,并将完成端口用于其异步I / O。因此,WriteAsync调用作为异步文件写入传递到操作系统。然后,操作系统与驱动程序堆栈进行通信,并传递数据以写入I / O请求数据包(IRP)。这是事情变得有趣的地方:如果设备驱动程序不能立即处理IRP,则它必须异步处理它。因此,驱动程序告诉磁盘开始写入,并向操作系统返回“待处理”响应。操作系统将该“挂起”响应传递给BCL,并且BCL将不完整的任务返回给请求处理代码。请求处理代码等待任务,该任务从该方法返回一个不完整的任务,依此类推。最后,请求处理代码最终将未完成的任务返回到ASP.NET,并且请求线程被释放以返回到线程池。请求处理代码等待任务,该任务从该方法返回一个不完整的任务,依此类推。最后,请求处理代码最终将未完成的任务返回到ASP.NET,并且请求线程被释放以返回到线程池。请求处理代码等待任务,该任务从该方法返回一个不完整的任务,依此类推。最后,请求处理代码最终将未完成的任务返回到ASP.NET,并且请求线程被释放以返回到线程池。
ASP.NET上的Async / Await简介
如果目标是提高可伸缩性(而不是响应性),则全部依靠外部I / O的存在来提供实现此目标的机会。