繁体   English   中英

不等待 Async 方法,该方法又在其中等待

[英]Not awaiting an Async method which in turn has an await in it

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

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

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

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

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

正确的

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

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

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

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

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

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

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

这都是关于 SynchronizationContext (和ConfigureAwait

行为将取决于当前SynchronizationContext

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

ConfigureAwait

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

参数

continueOnCapturedContext Boolean

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

WPF

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

DispatcherSynchronizationContext (WindowsBase.dll: System.Windows.Threading) WPF and Silverlight applications use a DispatcherSynchronizationContext, which queues delegates to the UI thread's Dispatcher with “Normal” priority. 当线程通过调用 Dispatcher.Run 开始其 Dispatcher 循环时,此 SynchronizationContext 被安装为当前上下文。 DispatcherSynchronizationContext 的上下文是单个 UI 线程。

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

例子

1. WPF,没有.ConfigureAwait

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

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);

Output

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();
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM