19

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

3 回答 3

51

我是 Stack Overflow 的新手(我的处女帖),但我很高兴你在问异步 CTP,因为我在 Microsoft 的团队中工作 :)

我想我理解你的目标,并且有几件事你做对了,让你到达那里。

我认为你想要什么:

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类型。相反,当 Playground API 发生冲突时,它们被命名为 FooEx。

为什么我们命名其中一些Run(...)和一些RunEx(...)?原因是我们在发布 CTP 时还没有完成方法重载的重新设计。在我们当前工作的代码库中,我们实际上不得不稍微调整 C# 方法重载规则,以便 Async Lambda 发生正确的事情——它可以返回voidTaskTask<T>.

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

因此,异步voidlambda 和异步Tasklambda 都支持return;不带参数。因此,需要澄清方法重载决议以决定选择哪一个。因此,您同时拥有 Run(...) 和 RunEx(...) 的唯一原因是,我们将确保在 PDC 2010 发布时为 Async CTP 的其他部分提供更高质量的支持。


如何考虑异步方法/lambdas

我不确定这是否是一个混淆点,但我想我会提到它 - 当您编写异步方法或异步 lambda 时,它可能具有调用它的人的某些特征。这取决于两件事:

  • 您正在等待的类型
  • 可能还有同步上下文(取决于上面)

await 的 CTP 设计和我们当前的内部设计都非常基于模式,因此 API 提供者可以帮助充实一组您可以“等待”的充满活力的东西。这可能因您正在等待的类型而异,常见的类型是Task.

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


注意使用 Wait() 方法

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

这样做是:

  1. 在周围的线程中同步调用TaskEx.Run(...) 在线程池上执行一个lambda。
  2. 为 lambda 指定了一个线程池线程,它调用您的异步方法。
  3. 从 lambda 调用异步方法 Test()。因为 lambda 在线程池上执行,所以 Test() 中的任何延续都可以在线程池中的任何线程上运行。
  4. lambda 实际上并没有腾出该线程的堆栈,因为它没有等待。在这种情况下,TPL 的行为取决于 Test() 是否在 Wait() 调用之前实际完成。但是,在这种情况下,您很有可能会在等待 Test() 在不同线程上完成执行时阻塞线程池线程。

这是“等待”运算符的主要好处是它允许您添加稍后执行的代码 - 但不会阻塞原始线程。在线程池的情况下,可以实现更好的线程利用率。

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

于 2011-02-12T13:39:10.010 回答
5

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

在这种情况下,您似乎并不真正希望该Test()方法是异步的 - 至少,您没有使用它是异步的事实。你只是在不同的线程中开始工作......该Test()方法可能是完全同步的,你可以使用:

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

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

于 2011-02-11T20:01:40.470 回答
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调用时“返回”时,程序会继续,并立即退出。这往往会搞砸一切,并产生意想不到的行为。

于 2011-02-11T19:58:16.227 回答