簡體   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