在另一个线程中运行异步函数
我正在评估Async CTP。
如何在另一个线程池的线程上开始执行异步函数?
static async Task Test() { // Do something, await something } static void Main( string[] args ) { // Is there more elegant way to write the line below? var t = TaskEx.Run( () => Test().Wait() ); // Doing much more in this same thread t.Wait(); // Waiting for much more then just this single task, this is just an example }
我是新的(我的处女post)Stack Overflow,但是我很高兴你问起Async CTP,因为我正在微软的团队中工作:)
我想我明白你的目标是什么,并且有一些你正确的事情,让你到那儿。
我想你想要什么:
static async Task Test() { // Do something, await something } static void Main(string[] args) { // In the CTP, use Task.RunEx(...) to run an Async Method or Async Lambda // on the .NET thread pool var t = TaskEx.RunEx(Test); // the above was just shorthand for var t = TaskEx.RunEx(new Func(Test)); // because the C# auto-wraps methods into delegates for you. // Doing much more in this same thread t.Wait(); // Waiting for much more then just this single task, this is just an example }
Task.Run与Task.RunEx
因为这个CTP安装在.NET 4.0之上,所以我们不想在mscorlib中修补实际的 System.Threading.Tasks.Task
类型。 相反,操场API在冲突时被命名为FooEx。
为什么我们将其中一些命名为Run(...)
和一些RunEx(...)
? 原因是由于我们在发布CTP时尚未完成的方法重载的重新设计。 在我们当前的工作代码库中,我们实际上必须稍微调整C#方法重载规则,以便Async Lambdas正确发生 – 它可以返回void
, Task
或Task
。
问题是当异步方法或lambdas返回Task
或Task
,它们实际上在返回表达式中没有外部任务类型,因为任务是作为方法或lambda调用的一部分自动生成的。 在我们看来,这对于代码清晰度来说似乎是正确的体验,尽管之前确实使事情变得非常不同,因为通常返回语句的表达式可以直接转换为方法或lambda的返回类型。
因此,async void
lambdas和async Task
lambdas都支持return;
没有争论。 因此需要澄清方法重载决策以决定选择哪一个。 因此,您同时拥有Run(…)和RunEx(…)的唯一原因是,在PDC 2010命中时,我们将确保为Async CTP的其他部分提供更高质量的支持。
如何考虑异步方法/ lambdas
我不确定这是否是一个混乱点,但我想我会提到它 – 当你编写异步方法或异步lambda时,它可以承担调用它的任何人的某些特征。 这取决于两件事:
- 您正在等待的类型
- 并且可能是同步上下文(取决于上面)
等待的CTP设计和我们当前的内部设计都是基于模式的,因此API提供商可以帮助充实您可以“等待”的一系列充满活力的东西。 这可能会根据您正在等待的类型而有所不同,其常见类型是Task
。
Task
的等待实现是非常合理的,并且遵循当前线程的SynchronizationContext
来决定如何推迟工作。 如果您已经在WinForms或WPF消息循环中,那么您的延迟执行将返回到同一消息循环(就像您使用BeginInvoke()
“您的方法的其余部分”)。 如果你等待一个任务并且你已经在.NET线程池上,那么“你的方法的其余部分”将在其中一个线程池线程上恢复(但不一定完全相同),因为它们被合并为开始时和您很可能很乐意使用第一个可用的池线程。
关于使用Wait()方法的注意事项
在您使用的示例中: var t = TaskEx.Run( () => Test().Wait() );
这样做是:
- 在周围的线程中同步调用TaskEx.Run(…)在线程池上执行lambda。
- 为lambda指定了一个线程池线程,它调用你的异步方法。
- 从lambda调用异步方法Test()。 因为lambda正在线程池上执行,所以Test()中的任何延续都可以在线程池中的任何线程上运行。
- lambda实际上没有腾出那个线程的堆栈,因为它没有等待它。 在这种情况下,TPL的行为取决于Test()实际上是在Wait()调用之前完成的。 但是,在这种情况下,您可能会在等待Test()完成在不同线程上执行时阻塞线程池线程。
‘await’运算符的主要好处是它允许您添加稍后执行的代码 – 但不会阻塞原始线程。 在线程池的情况下,您可以实现更好的线程利用率。
如果您对VB或C#的异步CTP有其他疑问,请告诉我,我很想听听他们:)
这通常取决于返回Task
以确定它运行的位置的方法,如果它正在开始真正的新工作,而不仅仅是捎带其他东西。
在这种情况下,看起来你真的不希望Test()
方法是异步的 – 至少,你没有使用它是异步的事实。 你只是在另一个线程中启动东西…… Test()
方法可以完全同步,你可以使用:
Task task = TaskEx.Run(Test); // Do stuff t.Wait();
这不需要任何异步CTP优点。
如果这不是控制台应用程序,那就会有。 例如,如果在Windows窗体应用程序中执行此操作,则可以执行以下操作:
// Added to a button click event, for example public async void button1_Click(object sender, EventArgs e) { // Do some stuff await Test(); // Do some more stuff }
但是,控制台中没有默认的SynchronizationContext
,因此无法按照您的预期运行。 在控制台应用程序中,您需要显式获取任务,然后在最后等待。
如果您在Windows窗体,WPF甚至WCF服务的UI线程中执行此操作,则会有一个有效的SynchronizationContext,用于正确地封送结果。 但是,在控制台应用程序中,当await
调用“返回”控制时,程序继续,并立即退出。 这往往会弄乱一切,并产生意想不到的行为。