C# 7.1 的 Async Main()

  • 2192
  • 0

在 C# 5.0 開始加入了 Task 並且發明新的關鍵字 async, await,讓寫非同步的程式變得相當簡便.不僅好寫,程式碼也好讀.從那之後,.Net Framework 和相關工具只要遇上 I/O 相關的 API 都會增加非同步的版本.例如寫資料到 StreamWriter 時多了 WriteLineAsync() 的非同步方式.

從那時開始便出現一個小挑戰.因為舊有的程式碼都是同步的,如果要加上非同步,這樣的改變將是一串的連鎖反應.例如,你某個 function 呼叫了一個非同步的程式碼,理論上來說你可能要在這個 function 裡用 await 或是 ContinueWith(). 如果你改用了 await,則這個 function 還必須上加 async,然後一連串地再往上改.如果你用了 ContinueWith(),雖然這 function 不用加 async,但預設上來說 ContinueWith() 的內容還沒執行時,calling thread 就會跳出去,稍後再由另一個 worker thread 來執行 ContinueWith() 的內容.所以,在整個程式裡某個地方一定要做 "等待",把非同步等成同步,這樣才不會造成連鎖效應一直往上改,因為改到最後可能會發現 Main() 是不能改非同步的 (假設你的程式是一個 Console app 或 WPF app 之類的).

在 C# 7.0 以前 (包含 C# 7.0),Main() 的 function signature 只有下列 4 種

static void Main()
static void Main(string[])
static int Main()
static int Main(string[])

所以要將 Main 改成非同步是不可能的.如果要讓 Main() 執行非同步的 API,唯一的辦法就是在那個非同步 API 上做等待,例如用 Wait() 或用 GetAwaiter().GetResult() 之類的.在 C# 7.1 裡幫你加了一個 Main() 的語法糖讓你可以在 Main() 裡呼叫非同步 API,也就是 Main() 可以回傳一個 Task.所以在 C# 7.1 多了下列的 function signatures

static Task Main()
static Task<int> Main()
static Task Main(string[])
static Task<int> Main(string[])

這樣一來,Main() 就可以變成是非同步的 Main() 了.如前面說的,這也只是一個語法糖,自己幫你把 Task Main() 變成去呼叫 Main().GetAwaiter().GetResult(),透過這樣的方式讓 Main() 可以執行非同步程式碼.

接著來看一個簡單的程式碼:

class Program
{
    static async Task Main(string[] args)
    {
        Test t = new Test();
        int result = await t.TestAsync();
        Console.WriteLine(result);
        Console.WriteLine("end");
        Console.Read();
    }
}

class Test
{
    public async Task TestAsync()
    {
        for (int i = 0; i < 5; i++)
        {
            using (StreamWriter sw = new StreamWriter("test.txt", true))
            {
                await sw.WriteLineAsync(i.ToString());
            }
        }
        return 1;
    }
}

Main() 可以回傳 Task 也可以設置 async,因此它裡面就可以用 await 來等待一個非同步 API 完成後再繼續後面的程式碼.

上面程式的 Task Main() 就是個語法糖,它跟以下的程式的 Main() 是相等的.

static void Main(string[] args)
{
    Main2(args).GetAwaiter().GetResult();
}

static async Task Main2(string[] args)
{
    Test t = new Test();
    int result = await t.TestAsync();
    Console.WriteLine(result);
    Console.WriteLine("end");
    Console.Read();
}

這功能大約在兩年前就被許多人提出來討論,現在終於在 C# 7.1 中看到它,C# 的語法糖又增加一員.

多了這一個新功能,這樣你就可以把你的非同步程式碼一路 async 到 Main() 了.如果你有興趣嘗試,請使用 Visual Studio 2017 Update 1 (15.3),並請確認你的專案屬性裡,C# 的版本是 7.1.

Hope it helps,