考虑这个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();
}
}
这是Foo
/ Bar
/ Boo
方法的链,我想异步执行,而不会阻塞UI线程。这些方法类似,从某种意义上讲,所有这些方法都会产生一些CPU限制的工作并最终调用“真正的”异步操作(例如,执行一些繁重的计算,将结果保存到数据库中)。
上面代码的输出是:
button1_Click正在UI线程上运行:真实 Foo在UI线程上运行:真实 Bar在UI线程上运行:真实 Boo在UI线程上运行:真实 完整。
button1_Click正在UI线程上运行:True
所以,所有这些东西都是同步执行的
我通过内置的等待来了解当前背景的capturing。所以,我想,只要这样打ConfigureAwait(false)
就足够了:
private async Task Foo()
{
await Task.Delay(0).ConfigureAwait(false);
DoCpuBoundWork();
await Bar();
}
但实际上,这并没有改变任何事情
我想知道,如何将这个“推送”到线程池线程,假设在button1_Click
方法结束时我需要返回UI线程?
修改
Task.Delay(0)
实际上优化了调用,当它的参数为0
时(感谢@usr为注释)。这样:
private async Task Foo()
{
await Task.Delay(1).ConfigureAwait(false);
DoCpuBoundWork();
await Bar();
}
将按预期工作(一切都在线程池上执行,但button1_Click
的代码除外)。但这更糟糕:捕获上下文或不捕获依赖于等待实施。
答案 0 :(得分:5)
你的await Task.Delay(0).ConfigureAwait(false);
是一个糟糕的勒芒Task.Yield()
尝试(这是行不通的,因为我猜如果参数为零,Delay会自行优化)。
我不建议在这里屈服。
我认为在你的点击处理程序中你应该推送到线程池:
await Task.Run(async () => await Foo());
这非常简单,如果您调用的方法不依赖于UI的同步上下文,则始终有效。这在体系结构上很好,因为您调用的方法不需要或应该知道它们被调用的代码。
答案 1 :(得分:0)
Task.Factory.StartNew()可用于在单独的线程中轻松运行代码。您可以选择通过调用Wait()来使当前线程等待第二个线程。
以下是一个快速示例程序,演示相关部分:
class Program
{
static void Main(string[] args)
{
Task t = Task.Factory.StartNew(() =>
{
//New thread starts here.
LongRunningThread();
});
Console.WriteLine("Thread started");
t.Wait(); //Wait for thread to complete (optional)
Console.WriteLine("Thread complete");
Console.ReadKey();
}
static void LongRunningThread()
{
Console.WriteLine("Doing work");
//do work here
}
}