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

Posted

技术标签:

【中文标题】在另一个线程中运行异步函数【英文标题】:Run an async function in another thread 【发布时间】:2011-06-25 19:03:15 【问题描述】:

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

【问题讨论】:

【参考方案1】:

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

【讨论】:

我使用控制台应用程序只是为了测试。我目前正在设计一个高度可扩展的集群(来自客户端的单个 WCF 调用可能导致不同集群节点之间最多 3 个 WCF 调用),我不会通过生成我自己的线程而不是仅仅破坏可扩展性将任务发布到 CLR 线程池。 @Snoonts:在 WCF 中,您将设置同步上下文,因此我的代码可以正常工作... 你能告诉更多吗?我需要在线程池中发布任务并继续运行。如果我只是写例如任务 t = Test();那么任务的第一部分将在同一个线程上执行,我不喜欢这样。我需要 整个 任务由线程池执行,包括任务的第一部分(在第一个 await 关键字之前)。 @Snoots:如果您使用我上面的代码,并且您的“任务”包含在 Test 方法中,那么 Test 中的所有内容都将在后台运行。【参考方案2】:

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

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

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

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

【讨论】:

TaskEx.Run(Test) 在 Test() 是异步方法时甚至无法编译。 @Soonts:如果它是一个返回 void 的方法(它可以是),它应该......但我的建议是它不应该 be 首先是异步的. 如果是返回 void 的方法,则 "t.Wait()" 不会等待,只执行 Test() 主体的第一部分(第一个 await 关键字之前的代码)。 【参考方案3】:

我是 Stack Overflow 的新手(我的处女帖),但我很高兴你问到 Async 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&lt;T&gt;

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

因此,async void lambdas 和 async Task lambdas 都支持不带参数的 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( () =&gt; Test().Wait() );

它的作用是:

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

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

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

【讨论】:

欢迎来到 Stack Overflow,感谢您的精彩解释!顺便说一句,我正在创建一个高度并行的集群,以使用 100GB 分布式数据库为 200k 并发用户提供服务。在我当前的设计原型中,集群节点通过双工 net.tcp 使用 WCF 相互通信(以及与客户端),来自客户端的单个调用可能导致不同集群节点之间最多 3 次调用。目前,我正在评估 Async CTP,因为我认为与 BeginXxx/EndXxx 方法相比,它会大大简化我们的代码,从而实现更简洁的实现。 你能看看我最近关于这个主题的问题吗?提前致谢! 感谢精彩简洁的解释!在几段中,您澄清了许多人对 async/await 的一些混淆点——即延续运行的地方。我的问题是:如果调用 await 的线程是控制台应用程序的主线程(没有消息泵),那么延续会抢占主线程吗?在什么时候?如果是这样,我们是否需要处理并发问题? 我不是 100% 确定你在主线程中的 pre-empt 是什么意思。默认情况下,控制台应用程序预先安装了一个空同步上下文,这将导致任务等待程序将继续调度到线程池,因为线程池能够接收预定的工作。 确实会导致您的控制台应用程序代码在其他线程上运行,但是,.NET 中 Async 的一大价值是仍然遵守评估顺序至少在您的异步方法中。例如,如果您的方法有多个等待,您不必担心我们会突然并行化您方法的这些部分。当然支持并行执行,但我们仍然要求您明确编码。

以上是关于在另一个线程中运行异步函数的主要内容,如果未能解决你的问题,请参考以下文章

任务在另一个线程 .NET 上异步运行

关于nodejs线程的一些困惑

标准 C++ 中的异步线程

将异步任务发送到在其他线程中运行的循环

Win32 函数可以异步运行在同一个线程上吗?

使用不同的变量异步多次运行某些代码