1. 程式人生 > 其它 >非同步、多執行緒、Await/Async、Task

非同步、多執行緒、Await/Async、Task

非同步多執行緒經常被拿來說事,網上文章也是多如牛毛,我也是停留在很菜的水平,痛下決心好好“惡補”一下這塊知識。
還是先放兩個官方文件壓壓驚:使用 Async 和 Await 的非同步程式設計.NET 中的並行處理、併發和非同步程式設計

學習前:

耗時操作知道搞個Task.Run,不用等待結果就後臺執行,Api直接返回,多個任務就迴圈啟用Task。控制器方法加上async,呼叫加上await,別人這麼用也跟著,不造為毛。Thread最常用的是Sleep。。。就這樣。

學習後:

多執行緒的前世今生就不具體介紹了,簡單粗暴的實操文章奉上:Thread、ThreadPool、Task、Parallel的基本用法、區別以及弊端


通過幾個小例子Get不明白的知識點,搞一個winform,啟動的同時再帶一個控制檯,方便輸出對比,Program.cs需要加幾行程式碼。大概放了這幾個按鈕,寫了幾個輸出和呼叫非同步的方法(完整程式碼在最後)。

1、同步

這就不說了,睡3秒順序執行,有一點注意是文字框資訊是沒有即時更新的,winform正常情況下是主執行緒更新控制元件狀態的,因為主執行緒被阻塞了,所以無法重新整理UI,主執行緒釋放後才執行。

2、非同步中更新UI

Over的時候可以發現更新資訊是由執行緒id為5的執行緒完成的,跨執行緒操作winform控制元件的方法交給委託,見紅框。

3、非同步結果更新UI

如果非同步執行緒需要等待返回值可以這樣寫更簡潔,方法上加上了async關鍵字,await等待執行緒執行,那麼問題來了,非同步不就又變成同步了?意義何在呢?這也是實操前一直不明白的點,這個例子看不出效果,接著操作。

4、呼叫非同步

呼叫非同步方法,自己本身也應該是非同步的,如果沒有async關鍵字,就會報出警告,並且沒有async修飾的方法不能使用await關鍵字,這個例子只是把非同步方法MethodAsync拿出去了,為了演示下面的例子使用。

5、非同步呼叫死鎖

這裡呼叫非同步方法MethodAsyncDeadlock,與MethodAsync的區別就是,把耗時函式的結果返回了,由於主執行緒沒有等待非同步呼叫返回,又輸出了非同步方法的結果,自然是拿不到返回值,因為非同步方法還沒有執行完成,所以這執行緒就“幹架”了,卡這了,死鎖。

6、解決死鎖

死鎖大部分是程式碼不規範造成的,程式碼寫懵逼也是常有的事,解決也很簡單,await出馬,等非同步方法完成返回再繼續執行就OK了。

7、Thread、Parallel、Task

啟動方式直接看程式碼,有一個小坑需要注意,就是多執行緒訪問公共變數問題。

8、重點來了!!!

非同步程式設計的意義何在這個哲學問題,換一個控制檯例子更能說明問題。很簡單,一個控制檯、一個類。從執行結果可以看出來,Main方法開始,到MethodAsync非同步方法執行,開啟Task耗時函式之前都是執行緒1在幹活兒,耗時函式由另外一個執行緒4執行,開始之前執行緒1已已經返回,並且輸出了Main End,這意味著剩下的不管是耗時函式,還是等待耗時函式完成之後的邏輯,都不在需要主執行緒等待完成,由別的執行緒搞定。winform解釋不來是因為非同步完成之後還是交給了主執行緒UI去繼續執行了,所以執行緒id總是前後一致。

9、結論!!!

實操分析完之後,就明白了之前忘記在哪看到的一句話了:非同步能增加網站的吞吐量,但並不會讓網站變得更快。
1、提高網站的響應能力,也是async/await也常見於Web專案的原因。
2、可以用同步的寫法,完成非同步操作,需要的時候啟動執行緒並行執行,提高運算或邏輯處理能力,又能等待控制順序,可以說賊爽。
參考了:C# 徹底搞懂async/await,也是言簡意賅的文章。

附上程式碼(粗陋勿噴)

public partial class AsyncDome : Form
{
    public AsyncDome()
    {
        InitializeComponent();
    }

    //同步
    private void button1_Click(object sender, EventArgs e)
    {
        btnClickAop(sender, "Start", true);
        Thread.Sleep(3000);
        btnClickAop(sender, "End");
    }
    //非同步中更新UI
    private void button2_Click(object sender, EventArgs e)
    {
        btnClickAop(sender, "Start", true);
        Task.Run(() =>
        {
            Thread.Sleep(3000);
            showConsoleDelegate(bt(sender) + " Over");
        });
        btnClickAop(sender, "End");
    }
    //非同步結果更新UI
    private async void button3_Click(object sender, EventArgs e)
    {
        btnClickAop(sender, "Start", true);
        var t = Task.Run(() =>
        {
            Thread.Sleep(3000);
            return bt(sender) + " Over";
        });
        showConsole(await t);
        btnClickAop(sender, "End");
    }
    //呼叫非同步
    private void button4_Click(object sender, EventArgs e)
    {
        btnClickAop(sender, "Start", true);
        MethodAsync();
        btnClickAop(sender, "End");
    }
    //非同步呼叫死鎖
    private void button5_Click(object sender, EventArgs e)
    {
        btnClickAop(sender, "Start", true);
        var ResultTask = MethodAsyncDeadlock();
        showConsole(ResultTask.Result); //死鎖原因:此行程式碼與呼叫方法搶佔主執行緒,導致死鎖。
        btnClickAop(sender, "End");
    }
    //解決死鎖
    private async void button6_Click(object sender, EventArgs e)
    {
        btnClickAop(sender, "Start", true);
        var ResultTask = MethodAsyncDeadlock();
        showConsole(await ResultTask); //死鎖解決:等待呼叫方法使用完成主執行緒即可。
        btnClickAop(sender, "End");
    }
    //Thread迴圈
    private void button7_Click(object sender, EventArgs e)
    {
        //Thread
        for (int i = 0; i < 100; i++)
        {
            int newIndex = i;
            new Thread(new ThreadStart(() => showConsoleDelegate("for: " + newIndex))).Start();
        }
    }
    //Parallel迴圈
    private void button8_Click(object sender, EventArgs e)
    {
        //Parallel
        Parallel.ForEach<int>(new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, (str) =>
        {
            showConsoleDelegate(str.ToString());
        });
    }
    //Task迴圈
    private void button9_Click(object sender, EventArgs e)
    {
        //Task
        for (int i = 0; i < 100; i++)
        {
            int newIndex = i;
            Task.Run(() => { showConsoleDelegate("for: " + newIndex); });
        }
    }

    #region 輸出處理
    private void btnClickAop(object sender, string flag, bool empty = false)
    {
        if (empty)
        {
            txtConsloe.Text = "";
            Console.Clear();
        }
        showConsole(bt(sender) + " " + flag);
    }

    private void showConsole(string info)
    {
        info = getInfo(info);
        txtConsloe.Text += info;
        Console.WriteLine(info);
    }

    private void showConsoleDelegate(string info)
    {
        info = getInfo(info);
        Console.WriteLine(info);
        txtConsloe.Invoke(new Action(() => { txtConsloe.Text += info; }));
    }
    //輸出資訊
    private string getInfo(string info)
    {
        return info + " Thread :" + Thread.CurrentThread.ManagedThreadId + " " + DateTime.Now.ToString("G") + Environment.NewLine;
    }
    //獲取按鈕文字
    private string bt(object sender)
    {
        return ((Button)sender).Text;
    }
    #endregion

    #region 非同步方法
    private async Task MethodAsync()
    {
        showConsole("MethodAsync Start");
        string Result = await LongTimeMethod();
        showConsole(Result);
        showConsole("MethodAsync End");
    }
    private async Task<string> MethodAsyncDeadlock()
    {
        showConsole("MethodAsyncDeadlock Start");
        string Result = await LongTimeMethod();
        showConsole("MethodAsyncDeadlock End");
        return Result;
    }

    private Task<string> LongTimeMethod()
    {
        var task = Task.Run(() =>
        {
            showConsoleDelegate("LongTimeMethod Start ");
            Thread.Sleep(3000);
            showConsoleDelegate("LongTimeMethod End ");
            return "LongTimeMethod Over";
        });
        return task;
    }
    #endregion
}