2019年2月4日 星期一

C# async 非同步方法需要額外花費多少時間成本之評估


C# async 非同步方法需要額外花費多少時間成本之評估

我們知道,當我們撰寫一個 async 方法的時候,就算我們只有撰寫一行 Console.Write 方法,編譯器也會幫我們產生出許多的 IL ( intermediate language ) 程式碼,也就是說,雖然我們僅有撰寫一行程式碼,面對一個非同步的方法,編譯器幫助我們產生了當要設計出一個非同步方法所需要用到的相關程式碼,這些程式碼主要是由一個狀態機 state machine 所組成,而且,編譯器會依據您正在設計的 async 方法,自動建立出其他的類別代碼;也就是說,當我們呼叫這個非同步方法的時候,將會透過編譯器所產生的程式碼來運作。

了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
了解更多關於 [Thread Class] 的使用方式
了解更多關於 [Task Class] 的使用方式

其中第一個就是要進行狀態機的初始化設定工作,在狀態機內將會開始呼叫 MoveNext 方法,開始執行這個非同步方法;在這個狀態機內將會知道當呼叫 await 關鍵字的時候,並且當所呼叫的非同步方法執行完成之後,該繼續從哪裡來執行點,而狀態機內也會有我們所設計的非同步方法的敘述,不過,會依據您所設計的程式碼,拆解成為不同的區塊,但是,您不用擔心,狀態機將會把這些程式碼串接起來,並且也會處理當有例外異常發生的時候,把這些例外異常捕捉起來,記錄在工作物件內。
現在,我們要來看看當我們使用了 async 修飾詞之後,會有甚麼影響?這包含了在 async 方法內沒有使用 await 關鍵字與有使用 await 關鍵字的執行效能。
首先,我們來撰寫個同步方法的呼叫花費時間測試,測試程式碼如下:
在我的電腦上,螢幕輸出結果為 呼叫 1000 次 同步方法 的平均花費時間 : 0.0001087 ms ,好的,我們接下來繼續做下一個測試。
C Sharp / C#
class Program
{
    static void Main(string[] args)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < 1000; i++)
        {
            string foo = 同步方法();
        }
        sw.Stop();
        double cost = sw.Elapsed.TotalMilliseconds / 1000.0;
        Console.WriteLine($"呼叫 1000 次 同步方法 的平均花費時間 : {cost} ms");
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    static string 同步方法()
    {
        return "同步方法";
    }
}
我們來撰寫個空的非同步方法的呼叫花費時間測試,測試程式碼如下,在這裡的 空的非同步方法() 並沒有做任何事情,如同前面的同步方法,唯一的差異就是我們使用了 async 修飾詞在這個方法上,並也在這個 空的非同步方法() 內,也沒有使用任何的 await 關鍵字。
在我的電腦上,螢幕輸出結果為 呼叫 1000 次 同步方法 的平均花費時間 : 0.0014271 ms ,經過與前面的同步方法來比較,在同步方法前面加上 async 關鍵字之後,每次使用 await 還呼叫這個空的非同步方法,將會多花費 0.0013184ms 時間,也就是成長的 13.13倍,好的,我們接下來繼續做下一個測試。
C Sharp / C#
static async Task Main(string[] args)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < 1000; i++)
    {
        string foo = await 空的非同步方法();
    }
    sw.Stop();
    double cost = sw.Elapsed.TotalMilliseconds / 1000.0;
    Console.WriteLine($"呼叫 1000 次 同步方法 的平均花費時間 : {cost} ms");
    Console.WriteLine("Press any key for continuing...");
    Console.ReadKey();
}
static async Task<string> 空的非同步方法()
{
    return "同步方法";
}
現在,在空的非同步方法內,使用 await Task.Yield() 進行非同步方法的呼叫,讓我們來看看這樣的做法究竟會花費多少時間,測試程式碼如下:
其中,我們使用 await 等候的 Task.Yield 方法,其功能為: 您可以使用await Task.Yield();中非同步的方法,以強制以非同步方式完成的方法,我們是用來模擬一個等候非同步工作,但是又沒有要做甚麼事情的情境。
在我的電腦上,螢幕輸出結果為 呼叫 1000 次 同步方法 的平均花費時間 : 0.361438 ms ,很明顯的,這樣的做法比起空的非同步方法來說,多花費了 0.3600109 ms 時間,也就是成長了 253.27 倍之多。
C Sharp / C#
class Program
{
    static async Task Main(string[] args)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < 1000; i++)
        {
            string foo = await 立即返回非同步方法();
        }
        sw.Stop();
        double cost = sw.Elapsed.TotalMilliseconds / 1000.0;
        Console.WriteLine($"呼叫 1000 次 同步方法 的平均花費時間 : {cost} ms");
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    static async Task<string> 立即返回非同步方法()
    {
        await Task.Yield();
        return "同步方法";
    }
}
透過這樣的測試過程,同步方法的執行效能,比起非同步方法的執行效能,可以說來的快得多,可是,像要設計一個非同步方法,會相當的麻煩與不好維護程式碼,但是透過了 TPL & async & await 的幫助,可以讓我們使用同步程式碼設計邏輯,設計出具有非同步方法應用的成果,不過,我們所要付出的將會是一點點執行效能的犧牲。


了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
了解更多關於 [Thread Class] 的使用方式
了解更多關於 [Task Class] 的使用方式







沒有留言:

張貼留言