都是关于 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();
}