Den*_*nis 3 c# wpf winforms async-await
考虑这个Windows窗体代码(可以编写类似的WPF模拟代码):
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void TraceThreadInfo([CallerMemberName]string callerName = null)
{
Trace.WriteLine($"{callerName} is running on UI thread: {!this.InvokeRequired}");
}
private void DoCpuBoundWork([CallerMemberName]string callerName = null)
{
TraceThreadInfo(callerName);
for (var i = 0; i < 1000000000; i++)
{
// do some work here
}
}
private async Task Foo()
{
DoCpuBoundWork();
await Bar();
}
private async Task Bar()
{
DoCpuBoundWork();
await Boo();
}
private async Task Boo()
{
DoCpuBoundWork();
// e.g., saving changes to database
await Task.Delay(1000);
}
private async void button1_Click(object sender, EventArgs e)
{
TraceThreadInfo();
await Foo();
Trace.WriteLine("Complete.");
TraceThreadInfo();
}
}
Run Code Online (Sandbox Code Playgroud)
这是我想要异步执行的Foo/ Bar/ Boo方法链,而不会阻塞UI线程.这些方法类似,从某种意义上讲,所有这些方法都会产生一些CPU限制的工作并最终调用"真正的"异步操作(例如,执行一些繁重的计算,将保存结果发送到数据库).
上面代码的输出是这样的:
button1_Click在UI线程上运行:True
Foo在UI线程上运行:True
Bar在UI线程上运行:True
Boo在UI线程上运行:True
Complete.
button1_Click正在UI线程上运行:True
所以,所有这些东西都是同步执行的.
我知道通过内置的等待来捕获当前的上下文.所以,我想,这样打电话就足够了ConfigureAwait(false):
private async Task Foo()
{
await Task.Delay(0).ConfigureAwait(false);
DoCpuBoundWork();
await Bar();
}
Run Code Online (Sandbox Code Playgroud)
但实际上,这并没有改变任何事情.
我想知道,这怎么可以"推"到线程池线程,假设,在button1_Click方法结束时我需要返回到UI线程?
编辑.
Task.Delay(0)当它的参数是0(感谢@usr为注释)时,实际上优化了调用.这个:
private async Task Foo()
{
await Task.Delay(1).ConfigureAwait(false);
DoCpuBoundWork();
await Bar();
}
Run Code Online (Sandbox Code Playgroud)
将按预期工作(一切都在线程池上执行,除了button1_Click代码).但这更糟糕:捕获上下文或不捕获取决于等待实施.
你await Task.Delay(0).ConfigureAwait(false);是一个糟糕的勒芒Task.Yield()尝试(这是行不通的,因为我猜如果参数为零,延迟会优化自己).
我不建议在这里收益.
我认为在你的点击处理程序中你应该推送到线程池:
await Task.Run(async () => await Foo());
Run Code Online (Sandbox Code Playgroud)
这非常简单,如果您调用的方法不依赖于UI的同步上下文,则始终有效.这在架构上很好,因为您调用的方法不需要或应该知道它们被调用的代码.