【问题标题】:What code is actually executed "multi-threadedly" in async/await pattern?什么代码实际上是在异步/等待模式中“多线程”执行的?
【发布时间】:2016-04-18 21:56:34
【问题描述】:

在这段代码中:

public async Task v_task()
{
    await Task.Run(() => Console.WriteLine("Hello!"));
}

public async void v1()
{
    await v_task();
    // some other actions...
}

public void ButtonClick()
{
    v1();

    Console.WriteLine("Hi!");
}

如果ButtonClick被调用,上面哪些方法实际上是在async/await生成的底层线程池中并行执行的?

我的意思是,对于使用 async/await 的竞争条件,我应该担心什么?所有异步方法都必须在同一个调用者的线程中执行吗?我应该在可能的共享状态上使用互斥锁吗?如果是,我如何检测共享状态对象是什么?

【问题讨论】:

  • 默认情况下,所有异步延续都在它们启动的同一个同步上下文中执行。在控制台应用程序中没有同步上下文,所有任务都将在线程池中运行。所以是的,你应该注意比赛条件。共享状态对象是所有任务中正在访问的对象。
  • 你好@VMAtm!那么,调用图将在相同的同步上下文中执行(我认为这与线程相同),直到找到第一个“等待”关键字?此时(执行调用图中的第一个“等待”)“等待”下面的代码将在不同的线程中执行?

标签: c# multithreading asynchronous async-await


【解决方案1】:

如果ButtonClick被调用,上面哪些方法实际上是在async/await生成的底层线程池中并行执行的?

Task.Run 中的Console.WriteLine

我的意思是,我对使用 async/await 的竞争条件有什么顾虑?

我建议您先阅读我的async intro,它解释了await 的实际工作原理。

总之,async 方法通常以串行异步方式编写。以这段代码为例:

CodeBeforeAwait();
await SomeOtherMethodAsync();
CodeAfterAwait();

你总是可以说CodeBeforeAwait 将首先执行到完成,然后SomeOtherMethodAsync 将被调用。然后我们的方法将(异步)等待SomeOtherMethodAsync 完成,然后才会调用CodeAfterAwait

所以它是串行异步的。它以串行方式执行,就像您期望的那样,但在该流程中也有一个异步点(await)。

现在,您不能CodeBeforeAwaitCodeAfterAwait 将在同一个线程中执行,至少在没有更多上下文的情况下不会。默认情况下,await 将在当前的SynchronizationContext 中恢复(如果没有 SyncCtx,则为当前的TaskScheduler)。因此,如果上面的示例方法是在 UI 线程中执行的,那么您就会知道 CodeBeforeAwaitCodeAfterAwait 都将在 UI 线程中执行。但是,如果它在没有上下文的情况下执行(即,从后台线程或控制台主线程),则 CodeAfterAwait 可能会在不同的线程上运行。

请注意,即使方法的某些部分在不同的线程上运行,运行时也会在继续执行该方法之前设置任何障碍,因此无需围绕变量访问设置障碍。

另请注意,您的原始示例使用Task.Run,它明确地将工作置于线程池中。这与async/await 完全不同,您肯定必须将其视为多线程。

我应该在可能的共享状态上使用互斥锁吗?

是的。例如,如果您的代码使用Task.Run,那么您需要将其视为单独的线程。 (请注意,使用await很多更容易不与其他线程共享状态 - 如果您可以保持后台任务纯净,它们更容易处理。 p>

如果是,我如何检测共享状态对象是什么?

与任何其他类型的多线程代码的答案相同:代码检查。

【讨论】:

  • 嘿斯蒂芬,感谢您的非常好的回答!所以,总结一下:调用图中的所有等待调用都将在同一个线程中执行;如果我通过 Task.Run 或 TaskCompletionSource 显式创建任务,代码只会在另一个后台线程中执行?换句话说,如果我没有实现任何将通过 Task.Run 执行的方法,我的所有代码都将在同一个线程中执行吗?伙计,如果最后一个问题的答案是肯定的:这非常漂亮!非常感谢,我现在就去看看你的文章。
  • @ptr0x:不完全;它将在相同的 context 中执行。如果您只使用 UI 上下文,那么是的,这与“同一线程”具有相同的含义。但是,如果您在控制台应用程序中运行相同的代码,那么不,它可以是不同的线程。如果您的代码显式覆盖了默认的上下文捕获行为(即ConfigureAwait(false)),那么它可能会使用上下文,也可能会在线程池线程上运行。
  • 知道了。因此,如果我在不同的线程中显式运行相同的代码或通过 Task.Run 启动任务(它将在线程池线程中执行任务),我只需要处理可能的竞争条件,对吗?
  • 我刚看了你的文章。非常有帮助,我在那里找到了我需要的所有答案。再次:非常感谢!
【解决方案2】:

如果你调用一个异步函数,你的线程会执行这个函数直到它到达一个等待。

如果您没有使用 async-await,则线程将产生处理,直到等待的代码完成并继续执行等待后的语句。

但是当你使用 async-await 时,你告诉编译器,每当线程必须等待某事时,你可以做一些其他事情而不是等待,线程会做其他事情,直到你说:现在等你原来的东西完成。

由于调用了一个异步函数,我们确信内部的某个地方应该有一个等待。请注意,如果您调用不等待的异步函数,则会收到编译器警告,该函数将同步运行。

例子:

private async void OnButton1_clickec(object sender, ...)
{
    string dataToSave = ...;
    var saveTask = this.MyOpenFile.SaveAsync(dataToSave);

    // the thread will go into the SaveAsync function and will
    // do all things until it sees an await.
    // because of the async SaveAsync we know there is somewhere an await
    // As you didn't say await this.MyOpenfile.SaveAsync
    // the thread will not wait but continue with the following
    // statements:

    DoSomethingElse()
    await saveTask;

    // here we see the await. The thread was doing something else,
    // finished it, and now we say: await. That means it waits until its
    // internal await is finished and continues with the statements after
    // this internal await.

请注意,即使 SaveAsync 中某处的 await 已完成,线程也不会执行下一条语句,直到您 await SaveTask。这样做的效果是,如果 SaveAsync 中的等待完成,DoSomethingElse 将不会被中断。

因此,通常创建一个不返回 Task 或 Task 的异步函数是没有用的 唯一的例外是事件处理程序。 GUI 不必等到事件处理程序完成。

【讨论】:

  • 非常感谢哈拉德!我会接受上面斯蒂芬的回复作为答案,只是因为他的回复更完整。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-21
  • 2022-01-26
  • 1970-01-01
  • 2015-11-05
  • 1970-01-01
  • 1970-01-01
  • 2018-01-24
相关资源
最近更新 更多