不等待异步方法,而异步方法又在其中等待

时间:2019-11-12 23:07:24

标签: c# asynchronous

public void button_push()
{
    genericMethodAsync();
    SynchronousCode1();
}

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

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

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

3 个答案:

答案 0 :(得分:2)

  

我认为,因为单击按钮不会等待该方法,所以SynchronousCode1()可能在SynchronousCode2()之前执行。

正确

  

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

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

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

答案 1 :(得分:0)

这全都是关于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_push async,使genericMethodAsync返回Task,并使事情更可预测。

public async void button_push()
{
    await genericMethodAsync();
    SynchronousCode1();
}

async Task genericMethodAsync()
{
     await someOtherAsyncMethodAsync();
     SynchronousCode2();
     SyncronousCode3();
}

答案 2 :(得分:0)

  

我认为由于按钮单击不会等待该方法,因此SynchronousCode1()可能会在SynchronousCode2()之前执行。

是的,这是正确的。这将取决于someOtherAsyncMethod()的实现。但是,假设该方法不能同步完成(如其权利),那么它将最终产生,从而使await someOtherAsyncMethod()产生给调用者,这将允许在可能之前调用SynchronousCode1()方法someOtherAsyncMethod()方法已经完成(并且肯定在此之前,只要someOtherAsyncMethod()的异步完成有相当长的时间)。

  

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

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