【问题标题】:Not awaiting an Async method which in turn has an await in it不等待 Async 方法,该方法又在其中等待
【发布时间】:2019-11-13 09:27:09
【问题描述】:
public void button_push()
{
    genericMethodAsync();
    SynchronousCode1();
}

public async void genericMethodAsync()
{
     await someOtherAsyncMethod();
     SynchronousCode2();
     SyncronousCode3();
}

假设我有一个调用异步方法的按钮,但不等待它。但是,按钮调用的异步方法确实有等待。
我假设由于按钮单击不等待方法,SynchronousCode1() 可能在SynchronousCode2() 之前执行。

假设等待的异步方法需要很长时间,比如 2 秒,并且是一个 IO 绑定任务。 SynchronousCode2() 方法会在调用 button_push() 方法的同一个线程(或者我应该说同一个上下文)上完成吗?
我不确定这一点,因为 button_Push() 命令可能会在异步方法中的等待完成之前运行完成。

【问题讨论】:

  • void button_push 不涉及异步代码,因此没有理由切换线程。 void genericMethodAsync 包含一个捕获上下文的await,因此SynchronousCode2 应该在该上下文上执行。因此,SynchronousCode1SynchronousCode2 应该在相同的上下文/线程上运行。不过,这不是这样做的理由。
  • 您无法为此类代码获得任何保修。每个人喜欢 忽略但绝不应该忽略的细节是,当用户在这两秒钟到期之前关闭窗口时会发生什么。或者再次按下按钮。
  • @Hans: "当用户关闭窗口时会发生什么" -- 只有当关闭的窗口导致线程的消息循环终止时才有意义。许多窗口不会,并且就何时以及如何发生这种情况进行讨论会使讨论变得不必要地复杂化。我认为为了简单起见,“忽略”这样的问题是合理的。
  • 我知道这是一种糟糕的做法。我已经阅读了所有被认为是使用 async/await 的“正确”方式的做法。我或多或少地试图了解各种情况下发生的事情,尽管它们涉及好的或坏的做法。感谢您的评论!

标签: c# asynchronous


【解决方案1】:

我假设由于按钮单击不等待方法,SynchronousCode1() 可能在 SynchronousCode2() 之前执行。

正确

假设等待的异步方法需要很长时间,比如 2 秒,并且是一个 IO 绑定任务。 SynchronousCode2() 方法会在调用 button_push() 方法的同一个线程(或者我应该说同一个上下文)上完成吗?

button_push 方法中的所有内容都将在同一个线程上执行。因为它从不使用await,所以它永远不会产生控制权。它所做的只是将新任务发布到队列中,然后继续执行其余代码。

至于SynchronousCode2(),是的,它有可能在不同的线程或不同的上下文中执行。这完全取决于同步上下文的工作方式。对于控制台应用程序(除了线程池之外没有同步上下文),它很可能位于不同的线程上。在 .NET 框架上运行的 ASP.NET 应用程序中,它将位于同一线程(受thread agility 约束)和同一上下文中。在 .NET Core 应用程序 it could be different 中。在 WinForms 应用程序中(同步由默认消息泵提供),它将是相同的。

【讨论】:

    【解决方案2】:

    我假设由于按钮单击不等待方法,SynchronousCode1() 可能在SynchronousCode2() 之前执行。

    是的,没错。这将取决于someOtherAsyncMethod() 的实现。但是假设该方法没有同步完成(这是正确的),那么它最终将屈服,导致await someOtherAsyncMethod() 屈服于调用者,这将允许调用SynchronousCode1() 方法,可能在someOtherAsyncMethod() 之前方法已经完成(并且肯定在此之前,只要 someOtherAsyncMethod() 的异步完成时间长短)。

    假设等待的异步方法需要很长时间,比如 2 秒,并且是一个 IO 绑定任务。 SynchronousCode2() 方法会在调用 button_push() 方法的同一个线程(或者我应该说同一个上下文)上完成吗?

    鉴于您发布的代码,并假设代码在典型的 UI 线程中执行(即具有线程特定同步上下文的线程),那么是的,SynchronousCode2() 方法将在同一个线程中执行最初调用 button_push() 方法的位置。

    【讨论】:

      【解决方案3】:

      都是关于 SynchronizationContext(和 ConfigureAwait

      行为将取决于当前的SynchronizationContext

      此类实现的同步模型的目的是允许公共语言运行时的内部异步/同步操作在不同的同步模型下正常运行。

      ConfigureAwait

      使用ConfigureAwait,您可以影响延续行为。

      参数

      continueOnCapturedContext布尔值

      true 尝试将延续编组回捕获的原始上下文;否则,false

      WPF

      您的示例可能来自 WPF 应用程序,并将使用 DispatcherSynchronizationContext。这是一段来自 Parallel Computing - It's All About the SynchronizationContext 关于它。

      DispatcherSynchronizationContext (WindowsBase.dll: System.Windows.Threading) WPF 和 Silverlight 应用程序使用 DispatcherSynchronizationContext,它将委托以“正常”优先级排列到 UI 线程的 Dispatcher。当线程通过调用 Dispatcher.Run 开始其 Dispatcher 循环时,此 SynchronizationContext 被安装为当前上下文。 DispatcherSynchronizationContext 的上下文是单个 UI 线程。

      排队到 DispatcherSynchronizationContext 的所有委托由特定的 UI 线程按照它们排队的顺序一次执行一个。当前实现为每个顶级窗口创建一个 DispatcherSynchronizationContext,即使它们都共享相同的底层 Dispatcher。

      示例

      1。 WPF,没有.ConfigureAwait

      在没有.ConfigureAwait(false) SynchronousCode2 的 WPF 应用程序的上下文中,将在同一线程中执行。

      public partial class MainWindow : Window
      {
          public MainWindow()
          {
              InitializeComponent();
          }
      
          private void Button_Click(object sender, RoutedEventArgs e)
          {
              Print($"SynchronizationContext.Current: {SynchronizationContext.Current}");
              Print("\t button_push START");
              genericMethodAsync();
              Print("\t button_push END");
          }
      
          public async void genericMethodAsync()
          {
              Print("\t\t genericMethodAsync START");
              await someOtherAsyncMethod();
              Print("\t\t genericMethodAsync calling SynchronousCode2");
              SynchronousCode2();
              Print("\t\t genericMethodAsync END");
          }
      
          private void SynchronousCode2()
          {
              Print("\t\t\t SynchronousCode2 START");
              Print("\t\t\t SynchronousCode2 END");
          }
      
          private async Task someOtherAsyncMethod()
          {
              Print("\t\t\t someOtherAsyncMethod START");
              await Task.Delay(TimeSpan.FromSeconds(2));
              Print("\t\t\t someOtherAsyncMethod END");
          }
      
          private static void Print(string v) =>
              Console.WriteLine($"T{System.Threading.Thread.CurrentThread.ManagedThreadId}: {v}");
      }
      
      T1: SynchronizationContext.Current: System.Windows.Threading.DispatcherSynchronizationContext
      T1:      button_push START
      T1:          genericMethodAsync START
      T1:              someOtherAsyncMethod START
      T1:      button_push END
      T1:              someOtherAsyncMethod END
      T1:          genericMethodAsync calling SynchronousCode2
      T1:              SynchronousCode2 START
      T1:              SynchronousCode2 END
      T1:          genericMethodAsync END
      

      3。 WPF,带有.ConfigureAwait

      如果使用ConfigureAwait(false),则SynchronousCode2 可能会被另一个线程调用。

      await someOtherAsyncMethod().ConfigureAwait(continueOnCapturedContext: false);
      

      输出

      T1: SynchronizationContext.Current: System.Windows.Threading.DispatcherSynchronizationContext
      T1:      button_push START
      T1:          genericMethodAsync START
      T1:              someOtherAsyncMethod START
      T1:      button_push END
      T1:              someOtherAsyncMethod END
      T9:          genericMethodAsync calling SynchronousCode2
      T9:              SynchronousCode2 START
      T9:              SynchronousCode2 END
      T9:          genericMethodAsync END
      

      3。没有上下文

      在控制台应用程序的上下文中。结果会有所不同。 SynchronousCode2 可能会也可能不会被同一个线程执行。

      public void button_push()
      {
          Print("\t button_push START");
          genericMethodAsync();
          Print("\t button_push END");
      }
      
      public async void genericMethodAsync()
      {
          Print("\t\t genericMethodAsync START");
          await someOtherAsyncMethod();
          Print("\t\t genericMethodAsync calling SynchronousCode2");
          SynchronousCode2();
          Print("\t\t genericMethodAsync END");
      }
      
      private void SynchronousCode2()
      {
          Print("\t\t\t SynchronousCode2 START");
          Print("\t\t\t SynchronousCode2 END");
      
      }
      
      private async Task someOtherAsyncMethod()
      {
          Print("\t\t\t someOtherAsyncMethod START");
          await Task.Delay(TimeSpan.FromSeconds(2));
          Print("\t\t\t someOtherAsyncMethod END");
      }
      
      private static void Print(string v) =>
          Console.WriteLine($"T{System.Threading.Thread.CurrentThread.ManagedThreadId}: {v}");
      
      static void Main(string[] args)
      {
          Print(" Main START");
          new Program().button_push();
          Print(" Main after button_push");
          Console.ReadLine();
      }
      
      // .NETCoreApp,Version=v3.0
      T1: Main START
      T1: SynchronizationContext.Current: null
      T1:      button_push START
      T1:          genericMethodAsync START
      T1:              someOtherAsyncMethod START
      T1:      button_push END
      T1: Main after button_push
      T4:              someOtherAsyncMethod END
      T4:          genericMethodAsync calling SynchronousCode2
      T4:              SynchronousCode2 START
      T4:              SynchronousCode2 END
      T4:          genericMethodAsync END
      

      注意async avoid

      一般来说async void 应该避免,但事件处理程序是例外。

      另一个非常重要的规则是不要阻塞 UI 线程。

      你可以让button_pushasync,让genericMethodAsync返回Task,让事情变得更可预测。

      public async void button_push()
      {
          await genericMethodAsync();
          SynchronousCode1();
      }
      
      async Task genericMethodAsync()
      {
           await someOtherAsyncMethodAsync();
           SynchronousCode2();
           SyncronousCode3();
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-06-27
        • 1970-01-01
        • 1970-01-01
        • 2013-02-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多