据我了解,它本质上是一个轻量级线程。
不,这不是真的。在某些情况下,我可能Task
是真的,但这只是. 你可以通过传递一个委托来启动一个线程,并让它执行它(通常是异步的,可能是同步的,默认情况下使用线程池)。
使用线程的另一种方法是使用TaskCompletionSource
. 当您这样做时,任务(可能)不会创建任何线程、使用线程池或任何类似的东西。此模型的一个常见用法是将基于事件的 API 转换为基于任务的 API:
让我们假设,仅仅因为它是一个常见的例子,我们希望 aTask
将在From
我们拥有的 a 关闭时完成。已经有一个FormClosed
事件在该事件发生时触发:
public static Task WhenClosed(this Form form)
{
var tcs = new TaskCompletionSource<object>();
form.FormClosing += (_, args) =>
{
tcs.SetResult(null);
};
return tcs.Task;
}
我们创建一个TaskCompletionSource
,向相关事件添加一个处理程序,在该处理程序中,我们发出任务完成的信号,并TaskCompletionSource
为我们提供Task
返回给调用者的 a 。这Task
不会导致创建任何新线程,也不会使用线程池或类似的东西。
您可以使用这种看起来非常异步的构造来创建基于任务/事件的模型,但仅使用单个线程来完成所有工作(UI 线程)。
一般来说,只要你想要一个Task
代表除执行函数之外的东西的东西,你就会考虑使用TaskCompletionSource
. 它通常是解决问题的适当概念方法,而不是可能使用现有的 TPL 方法之一,例如WhenAll
,WhenAny
等。
我应该避免以这种方式滥用“异步/等待”模式吗?
不,因为这不是滥用。这是对Task
构造以及async/await
. 例如,考虑一下您可以使用我上面的辅助方法编写的代码:
private async void button1_Click(object sender, EventArgs e)
{
Form2 popup = new Form2();
this.Hide();
popup.Show();
await popup.WhenClosed();
this.Show();
}
这段代码现在可以正常工作了;创建一个新表单,隐藏自己,显示弹出窗口,等到弹出窗口关闭,然后再次显示自己。但是,由于它不是阻塞等待,因此 UI 线程不会被阻塞。我们也不需要为事件而烦恼;添加处理程序,处理多个上下文;移动我们的逻辑,或其中任何一个。(所有这些都发生了,只是对我们隐藏了。)