在另一个线程中运行异步函数

时间:2011-02-11 19:52:39

标签: c# .net c#-4.0 async-ctp

我正在评估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
}

3 个答案:

答案 0 :(得分:52)

我是新的(我的处女帖子)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<Task>(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正确的事情发生 - 它可以返回voidTaskTask<T>

问题是,当异步方法或lambdas返回TaskTask<T>时,它们实际上在返回表达式中没有外部任务类型,因为任务是作为一部分自动为您生成的方法或lambda的调用。这在我们看来似乎是正确的代码清晰度体验,尽管之前确实使事情完全不同,因为通常返回语句的表达式可以直接转换为方法或lambda的返回类型。

因此,async void lambdas和async Task lambdas都支持return;而没有参数。因此需要澄清方法重载决策以决定选择哪一个。因此,您同时拥有Run(...)和RunEx(...)的唯一原因是,在PDC 2010命中时,我们将确保为Async CTP的其他部分提供更高质量的支持。 / p>


如何考虑异步方法/ lambdas

我不确定这是否是一个混乱点,但我想我会提到它 - 当你编写异步方法或异步lambda时,它可以承担调用它的任何人的某些特征。这取决于两件事:

  • 您正在等待的类型
  • 可能是同步上下文(取决于上述内容)

等待的CTP设计和我们当前的内部设计都是基于模式的,因此API提供商可以帮助充实您可以“等待”的一系列充满活力的东西。这可能会根据您正在等待的类型而有所不同,其常见类型为Task

Task等待实现是非常合理的,并且按照当前线程的SynchronizationContext来决定如何推迟工作。如果您已经在WinForms或WPF消息循环中,那么您的延迟执行将返回到同一消息循环(就像您使用BeginInvoke()“其余方法”一样)。如果你等待一个Task并且你已经在.NET线程池中,那么“你的方法的其余部分”将在其中一个线程池线程上恢复(但不一定完全相同),因为它们被合并为开头很可能你很乐意使用第一个可用的池线程。


关于使用Wait()方法的注意事项

在您使用的样本中: var t = TaskEx.Run( () => Test().Wait() );

它的作用是:

  1. 在周围的线程中同步调用TaskEx.Run(...)在线程池上执行lambda。
  2. 为lambda指定了一个线程池线程,它会调用您的异步方法。
  3. 从lambda调用异步方法Test()。因为lambda正在线程池上执行,所以Test()中的任何延续都可以在线程池中的任何线程上运行。
  4. lambda实际上没有腾出那个线程的堆栈,因为它没有等待它。在这种情况下,TPL的行为取决于在Wait()调用之前Test()是否实际完成。但是,在这种情况下,您可能会在等待Test()在另一个线程上完成执行时阻塞线程池线程。
  5. 'await'运算符的主要好处是它允许您添加稍后执行的代码 - 但不会阻塞原始线程。在线程池的情况下,您可以实现更好的线程利用率。

    如果您对VB或C#的异步CTP有其他疑问,请告诉我,我很乐意听到它们:)

答案 1 :(得分:5)

通常由返回Task的方法确定它运行的位置,如果它正在开始真正的新工作,而不是仅仅依靠其他东西。

在这种情况下,它看起来不像真的希望Test()方法是异步的 - 至少,你不是使用这个事实这是异步的。你只是在另一个线程中启动... Test()方法可以完全同步,你可以使用:

Task task = TaskEx.Run(Test);
// Do stuff
t.Wait();

这不需要任何异步CTP优点。

答案 2 :(得分:2)

如果这不是控制台应用程序,那将会是。例如,如果在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调用时“返回”控制时,程序将继续,并立即退出。这往往会弄乱一切,并产生意想不到的行为。