如果您不想在方法内使用async/await,但仍然希望“装饰”它以便能够从外部使用await关键字,可以使用TaskCompletionSource.cs:
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;
}
从这里和这里开始
为了支持这样的任务范式,我们需要一种方式来保留任务外观和引用任意异步操作作为任务的能力,但根据提供异步性的基础结构的规则控制该任务的生命周期,并以不显著的方式完成。这就是TaskCompletionSource的目的。
我还看到它在.NET源代码中也被使用,例如 WebClient.cs:
[HostProtection(ExternalThreading = true)]
[ComVisible(false)]
public Task<string> UploadStringTaskAsync(Uri address, string method, string data)
catch
return tcs.Task;
}
最后,我还发现以下内容很有用:
我经常被问到这个问题。暗示着可能有某个线程正在阻塞对外部资源的I/O调用。所以,异步代码释放了请求线程,但只是以系统中其他地方的另一个线程为代价,对吧?不,完全不是这样。
为了理解为什么异步请求可以扩展,我将跟踪异步I/O调用的一个(简化)示例。假设请求需要写入文件。请求线程调用异步写方法。WriteAsync由基础类库(BCL)实现,并使用完成端口进行异步I/O。因此,WriteAsync调用作为异步文件写入传递给操作系统。然后,操作系统与驱动程序堆栈通信,将要写入的数据传递给I/O请求包(IRP)。
这就是事情变得有趣的地方:如果设备驱动程序无法立即处理IRP,则必须异步处理。因此,驱动程序告诉磁盘开始写入,并向操作系统返回“挂起”响应。操作系统将该“挂起”响应传递给BCL,而BCL会向请求处理代码返回不完整的任务。请求处理代码等待该任务,该任务从该方法返回不完整的任务,依此类推。最终,请求处理代码将返回不完整的任务给ASP.NET,并释放请求线程以返回到线程池。
ASP.NET上Async/Await的介绍
如果目标是提高可扩展性(而不是响应能力),这完全依赖于存在一个外部I/O,提供了这样的机会。