繁体   English   中英

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

[英]Push async method's execution to thread pool thread

考虑这个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线程上运行:True
Foo在UI线程上运行:True
Bar在UI线程上运行:True
Boo在UI线程上运行:True
完成。
button1_Click正在UI线程上运行:True

所以,所有这些东西都是同步执行的。
我知道通过内置的等待来捕获当前的上下文。 所以,我想,这样调用ConfigureAwait(false)就足够了:

    private async Task Foo()
    {
        await Task.Delay(0).ConfigureAwait(false);
        DoCpuBoundWork();
        await Bar();
    }

但实际上,这并没有改变任何事情。
我想知道,这怎么可以“推”到线程池线程,假设,在button1_Click方法结束时我需要返回到UI线程?

编辑

当参数为0时, Task.Delay(0)实际上优化了调用(感谢@usr为注释)。 这个:

    private async Task Foo()
    {
        await Task.Delay(1).ConfigureAwait(false);
        DoCpuBoundWork();
        await Bar();
    }

将按预期工作(一切都在线程池上执行,除了button1_Click的代码)。 但这更糟糕:捕获上下文或不捕获取决于等待实施。

await Task.Delay(0).ConfigureAwait(false); 是一个糟糕的mans Task.Yield()尝试(这是行不通的,因为我猜如果参数为零,Delay会自行优化)。

我不建议在这里收益。

我认为在你的点击处理程序中你应该推送到线程池:

await Task.Run(async () => await Foo());

这非常简单,如果您调用的方法不依赖于UI的同步上下文,则始终有效。 这在架构上很好,因为您调用的方法不需要或应该知道它们被调用的代码。

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

暂无
暂无

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

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