• 深入理解 C# 7.1 提供的 async 非同步 Main() 方法


    我們在開發 .NET 應用程式的時候,預設選取的 C# 語言版本為「最新已發行主要版本」(latest major version),如果以 Visual Studio 2017 v15.9.10 來說,內建的 C# 最新發行版本就是 7.3 版,因此主要版本就是 7.0 版。本篇文章要來介紹 C# 7.1 提供的一個語法糖,它能讓你的 Console 應用程式,將主程式的進入點 Main() 方法也能宣告為非同步(async)的版本,好讓你從頭到尾都用非同步的方式開發應用程式,最後還會說明這個新語法背後的技術原理。

    預設的 Console 應用程式

    通常剛建立好一個 Console 應用程式專案,其主程式 Program.cs 的內容如下:

     1 using System;
     2 using System.Threading.Tasks;
     3 
     4 namespace ConsoleApp
     5 {
     6   class Program
     7   {
     8     static void Main(string[] args)
     9     {
    10       // Your code here
    11     }
    12   }
    13 }

    由於 Visual Studio 2017 預設主控台應用程式的專案範本,預設僅支援到 C# 7.0 版,也就是說,你沒辦法寫出以下的程式碼,專案建置的時候會遇到 error CS5001: 程式未包含適合進入點的靜態 'Main' 方法 與 CS8107: C# 7.0 中未提供功能 '非同步主要'。請使用語言版本 7.1 或更高版本。 的錯誤訊息:

     1 using System;
     2 using System.Threading.Tasks;
     3 
     4 namespace ConsoleApp
     5 {
     6   class Program
     7   {
     8     static async Task Main(string[] args)
     9     {
    10       // Your code here
    11     }
    12   }
    13 }

    使用 async/await 開發 Console 應用程式

    解決這個問題還挺簡單,基本上有兩種方法:

    1. 呼叫 Task.Run(async () => { ... }).Wait(); 即可

      以下用一段簡單的 HttpClient 範例示範寫法:

     1 using System;
     2 using System.Net.Http;
     3 using System.Threading.Tasks;
     4 
     5 namespace ConsoleApp
     6 {
     7   class Program
     8   {
     9     static void Main(string[] args)
    10     {
    11       Task.Run(async () =>
    12       {
    13         using (var http = new HttpClient())
    14         {
    15           const string url = "http://docs.microsoft.com/";
    16           var body = await http.GetStringAsync(url);
    17           Console.WriteLine($"Size: {body.Length}");
    18         }
    19       }).Wait();
    20     }
    21   }
    22 }

    2. 切換到 C# 7.1 以上的語言版本

    無論你用 .NET Framework 或 .NET Core 的 Console App,都可以直接手動修改 *.csproj 專案檔,只要在第一個 <PropertyGroup> 底下加入 <LangVersion>latest</LangVersion> 即可:

    1 <LangVersion>latest</LangVersion>

    強烈建議手動編輯 *.csproj 加入 <LangVersion> 設定。

    成功加入之後,就可以直接以 async Task 來宣告 Main() 方法,以下用一段簡單的 HttpClient 範例示範寫法:

     1 using System;
     2 using System.Net.Http;
     3 using System.Threading.Tasks;
     4 
     5 namespace ConsoleApp
     6 {
     7   class Program
     8   {
     9     static async Task Main(string[] args)
    10     {
    11       using (var http = new HttpClient())
    12       {
    13         const string url = "http://docs.microsoft.com/";
    14         var body = await http.GetStringAsync(url);
    15         Console.WriteLine($"Size: {body.Length}");
    16       }
    17     }
    18   }
    19 }

    你也可以透過 msbuild /p:LangVersion=latest 命令,自動覆寫 csproj 檔案中的預設值。

    深入 static async Task Main(string[] args) 的技術細節

    光是調整設定好像有點無趣,我們來看看套用 C# 7.1 的 async Main 之後,C# 編譯器在背後偷偷做了什麼事!

    我先將上一段程式碼進行編譯,產生 ConsoleApp.exe 執行檔,並且用 ILSpy 進行反組譯分析。由於 C# 7.1 在編譯這段程式碼時,會自動對程式碼進行調整,之前有提過 C# 7.1 的 async Main 其實是個語法糖,也就是說程式碼在編譯完成後,將會跟原本的不太一樣。

    在進行分析之前,請先進行以下設定,開啟 Show all types and members 選項:

    我們要進行反組譯分析的第一步,就是找到程式的進入點。此時你會發現,原來 C# 7.1 並沒有真的將 static async Task Main(string[] args) 當成程式的進入點,而是另外建立一個 傳統的 Main() 方法,以這個 private static void <Main>(string[] args) 當做程式的進入點:

    1 private static void <Main>(string[] args)
    2 {
    3     Main(args).GetAwaiter().GetResult();
    4 }

    這裡的 <Main> 方法是由編譯器自動產生的方法,他呼叫了一個 Main() 方法,這個正是你在 Visual Studio 中撰寫的主程式,其回傳型別是 Task。然後直接呼叫了 Task.GetAwaiter Method 取得 TaskAwaiter,然後再呼叫 TaskAwaiter.GetResult Method 結束對非同步工作完成的等候。

    請注意:GetAwaiter() 與 GetResult() 都不建議自己寫在應用程式中,這兩個方法僅供編譯器使用。

    原始碼本身可以解釋一切,如果有興趣的話,可以進一步查看 <Main>d__0 這個類別的內容,這是一份 IAsyncStateMachine 的實作,用來控制 async/await 的執行流程,必要的時候還要翻開 IL 才能看懂整個執行過程。我相信透過這個反組譯的過程,可以幫助開發人員更加理解 async/await 的技術細節與設計原理,也能更能有自信的撰寫非同步的程式碼。

  • 相关阅读:
    配置PL/SQL Developer连接Oracle数据库
    解决RPC failed; HTTP 413 curl 22 The requested URL returned error: 413 Request Entity Too Large问题
    解决java.lang.IllegalArgumentException: No converter found for return value of type: class java.util.ArrayList问题
    解决spring mybatis 整合后mapper接口注入失败
    如何解决Failed to start component [StandardEngine[Catalina].StandardHost[127.0.0.1].StandardContext[]]问题
    五毛的cocos2d-x学习笔记05-场景与场景动画,动作
    五毛的cocos2d-x学习笔记04-触摸点
    五毛的cocos2d-x学习笔记03-控件
    五毛的cocos2d-x学习笔记02-基本项目源码分析
    五毛的cocos2d-x学习笔记01-创建项目
  • 原文地址:https://www.cnblogs.com/bisslot/p/12435940.html
Copyright © 2020-2023  润新知