我有一个库,它是许多网络连接的复杂仲裁者。它的主要对象的每个方法都有一个委托,当网络响应给定的请求时会调用该委托。
我想翻译我的库以使用新的.NET 4.5“async / await”模式;这将要求我返回一个“任务”对象,该对象将向用户发出呼叫的异步部分已完成的信号。创建这个对象需要一个代表任务的函数 - 据我所知,它本质上是一个轻量级的线程。
这并不适合我的库的设计 - 我希望任务的行为更像是一个事件,并直接向用户发出他们的请求已经完成的信号,而不是代表一个函数。这可能吗?我应该避免以这种方式滥用“异步/等待”模式吗?
我不知道我的措辞是否很好,我希望你理解我的意思。谢谢你的帮助。
答案 0 :(得分:7)
据我所知,它本质上是一个轻量级的线程。
不,那不是真的。在某些情况下,我可以是真的,但这只是Task
的一种用法。你可以通过传递一个委托来启动一个线程,让它执行它(通常是异步的,可能是同步的,默认情况下使用线程池)。
使用线程的另一种方法是使用TaskCompletionSource
。当您这样做时,任务(可能)不会创建任何线程,使用线程池或沿着这些线的任何东西。此模型的一个常见用法是将基于事件的API转换为基于任务的API:
让我们假设,仅仅因为它是一个常见的例子,我们希望在我们拥有的Task
关闭时完成From
。事件发生时,已发生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
返回给来电者。这个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线程不会被阻止。我们也不需要为事件烦恼;添加处理程序,处理多个上下文;移动我们的逻辑或任何一个。 (所有这一切都发生了,它只是隐藏在我们身上。)
答案 1 :(得分:4)
我想翻译我的库以使用新的.NET 4.5“async / await”模式;这将要求我返回一个“任务”对象,该对象将向用户发出呼叫的异步部分已完成的信号。
嗯,不是真的 - 你可以返回任何实现等待模式的东西 - 但Task
是最简单的方法。
这并不适合我的库的设计 - 我希望任务的行为更像是一个事件,并直接向用户发出他们的请求已经完成的信号,而不是代表一个函数。
您可以调用Task.ContinueWith
作为“处理程序”,以便在任务完成时执行。实际上,这就是TaskAwaiter
所做的事情。
说实话,你的问题并不十分清楚,但是如果你真的想创建一个Task
,你可以随时强制完成,我怀疑你只是想要TaskCompletionSource<TResult>
- 您调用SetResult
,SetCanceled
或SetException
方法来指示相应的完成类型。 (或者您可以调用TrySet...
版本。)使用Task
属性将任务返回到任何需要它。