简体   繁体   English

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

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

Consider this Windows Forms code (one could write similar WPF analogue): 考虑这个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();
    }
}

Here's the chain of Foo / Bar / Boo methods, which I want to execute asynchronously, without blocking of UI thread. 这是Foo / Bar / Boo方法的链,我想异步执行,而不阻塞UI线程。 These methods are similar, in sense that all of them makes some CPU-bound work and ultimately calls "true" asynchronous operation (eg, perform some heavy calculations an save result to the database). 这些方法类似,从某种意义上讲,所有这些方法都会产生一些CPU限制的工作并最终调用“真正的”异步操作(例如,执行一些繁重的计算,将保存结果发送到数据库)。

The output from the code above is this: 上面代码的输出是这样的:

button1_Click is running on UI thread: True button1_Click正在UI线程上运行:True
Foo is running on UI thread: True Foo在UI线程上运行:True
Bar is running on UI thread: True Bar在UI线程上运行:True
Boo is running on UI thread: True Boo在UI线程上运行:True
Complete. 完成。
button1_Click is running on UI thread: True button1_Click正在UI线程上运行:True

So, all this stuff executes synchronously. 所以,所有这些东西都是同步执行的。
I know about capturing of current context by built-in awaitables. 我知道通过内置的等待来捕获当前的上下文。 So, I thought, that it will be enough to call ConfigureAwait(false) like this: 所以,我想,这样调用ConfigureAwait(false)就足够了:

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

but, actually, this doesn't change anything. 但实际上,这并没有改变任何事情。
I'm wondering, how this can be "pushed" to thread pool thread, assuming, that at the end of button1_Click method I need to return to UI thread? 我想知道,这怎么可以“推”到线程池线程,假设,在button1_Click方法结束时我需要返回到UI线程?

Edit . 编辑

Task.Delay(0) actually optimizes call, when its argument is 0 (thanks to @usr for the note). 当参数为0时, Task.Delay(0)实际上优化了调用(感谢@usr为注释)。 This: 这个:

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

will works, as expected (everything executes on thread pool, except button1_Click 's code). 将按预期工作(一切都在线程池上执行,除了button1_Click的代码)。 But this is even worse: to capture context or to not capture depends on awaitable implementation. 但这更糟糕:捕获上下文或不捕获取决于等待实施。

Your await Task.Delay(0).ConfigureAwait(false); await Task.Delay(0).ConfigureAwait(false); is a poor mans Task.Yield() attempt (which does not work because I guess that Delay optimizes itself out if the argument is zero). 是一个糟糕的mans Task.Yield()尝试(这是行不通的,因为我猜如果参数为零,Delay会自行优化)。

I would not recommend yield here. 我不建议在这里收益。

I think in your click handler you should push to the thread-pool: 我认为在你的点击处理程序中你应该推送到线程池:

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

This is very simple and always works if the method you are calling does not depend on the synchronization context of the UI. 这非常简单,如果您调用的方法不依赖于UI的同步上下文,则始终有效。 This is architecturally nice because non of the methods you are calling need or should be aware by what code they are called. 这在架构上很好,因为您调用的方法不需要或应该知道它们被调用的代码。

Task.Factory.StartNew() can be used to easily run code in a separate thread. Task.Factory.StartNew()可用于在单独的线程中轻松运行代码。 You can optionally make the current thread wait for the second thread by calling Wait(). 您可以选择通过调用Wait()使当前线程等待第二个线程。

Below is a quick sample program which demonstrates the relevant pieces: 以下是一个快速示例程序,演示了相关部分:

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