将异步方法的执行推送到线程池线程

时间:2015-08-27 11:28:15

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

这是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的代码除外)。但这更糟糕:捕获上下文或不捕获依赖于等待实施。

2 个答案:

答案 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
    }
}