《C#併發編程經典實例》學習筆記—異步編程關鍵字 Async和Await

C# 5.0 推出async和await,最早是.NET Framework 4.5引入,可以在Visual Studio 2012使用。在此之前的異步編程實現難度較高,async使異步編程的實現變得簡便。

各平臺對async的支持情況

平臺 async
.NET 4.5及以上
.NET 4.0 NuGet
Mono iOS/Droid
Windows Store
Windows Phone Apps 8.1
Windows Phone SL 8.0
Windows Phone SL 7.1 NuGet
Silverlight 5 NuGet

在不支持的平臺,安裝NuGet包 Microsoft.Bcl.Async

使用 async 修飾符可將方法、lambda 表達式或匿名方法指定爲異步。

async 對方法做了什麼處理

從使用async修飾符修飾的方法的IL代碼可以得出一個結論:

  • 在Debug下,針對async方法,生成的是一個class狀態機
  • 在Release下,針對async方法,生成的是一個struct狀態機

舉例:
C#代碼如下

using System.Threading.Tasks;

namespace ConsoleApp3
{
    public class Test
    {
        public async Task TestAsync()
        {
            await GetAsync();
        }

        public async Task GetAsync()
        {
            await Task.Delay(1);
        }
    }
}

以TestAsync方法爲準

Release下 初始化狀態機V_0 ,類型是值類型Struct(valuetype),類型名稱爲<TestAsync>d__0

    .locals init (
      [0] valuetype ConsoleApp3.Test/'<TestAsync>d__0' V_0,
      [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder V_1
    )

<TestAsync>d__0 繼承值類型[mscorlib]System.ValueType

.class nested private sealed auto ansi beforefieldinit 
    '<TestAsync>d__0'
      extends [mscorlib]System.ValueType
      implements [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine

Debug 下 初始化狀態機V_0 ,類型是引用類型Class(class) ,類型名稱爲<TestAsync>d__0

    .locals init (
      [0] class ConsoleApp3.Test/'<TestAsync>d__0' V_0,
      [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder V_1
    )

<TestAsync>d__0 繼承引用類型[mscorlib]System.Object

  .class nested private sealed auto ansi beforefieldinit 
    '<TestAsync>d__0'
      extends [mscorlib]System.Object
      implements [mscorlib]System.Runtime.CompilerServices.IAsyncStateMachine

異步方法的定義和注意事項

使用 async 關鍵字定義的異步方法簡稱爲“異步方法”。

注意事項:

  • 如果 async 關鍵字修改的方法不包含 await 表達式或語句,則該方法將同步執行。 編譯器警告將通知你不包含 await 語句的任何異步方法,因爲該情況可能表示存在錯誤。 請參閱編譯器警告(等級 1)CS4014
  • async 關鍵字是上下文關鍵字,原因在於只有當它修飾方法、lambda 表達式或匿名方法時,它纔是關鍵字。 在所有其他上下文中,都會將其解釋爲標識符。
  • 不要用 void 作爲 async 方法的返回類型! async 方法可以返回 void ,但是這僅限於編寫事件處理程序。一個普通的 async 方法如果沒有返回值,要返回Task ,而不是 void
  • 一定要避免使用Task.WaitTask<T>.Result 方法,因爲它們會導致死鎖。如果使用了 async ,最好就一直使用它。
  • 異步方法的參數不能使用outrefoutref 返回的數據應借用Task<TResult> 返回,可以使用元組或自定義數據結構。

異步方法的特徵

  • 方法簽名包含 async 修飾符。
  • 按照約定,異步方法的名稱以“Async”後綴結尾。
  • 返回類型爲下列類型之一:
    • 如果你的方法有操作數爲 TResult 類型的返回語句,則爲 Task<TResult>
    • 如果你的方法沒有返回語句或具有沒有操作數的返回語句,則爲 Task
    • void:如果要編寫異步事件處理程序。
    • 包含 GetAwaiter 方法的其他任何類型(自 C# 7.0 起)。
  • 方法通常包含至少一個 await 表達式,該表達式標記一個點,在該點上,直到等待的異步操作完成方法才能繼續。 同時,將方法掛起,並且控制返回到方法的調用方。

關於async和await具體的執行流程,方法何時掛起和釋放,請參考異步程序中的控制流 (C#)

異步返回類型

上面提到 void 作爲返回結果,適用於事件處理程序。
舉例:

using System;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    public class TestVoidAsync
    {
        private event EventHandler<EventArgs> DoTest;

        public TestVoidAsync()
        {
            DoTest += DoTestEvent;
        }

        private static async void DoTestEvent(object sender, EventArgs e)
        {
            await Task.Delay(1000);
        }

        protected virtual void OnDoTest()
        {
            DoTest?.Invoke(this, EventArgs.Empty);
        }
    }
}

void 作爲返回結果存在一個弊端:無法捕獲異常。

返回 void 的異步方法的調用方無法捕獲從該方法引發的異常,且此類未經處理的異常可能會導致應用程序故障。 如果返回 TaskTask<TResult> 的異步方法中出現異常,此異常將存儲於返回的任務中,並在等待該任務時再次引發。

通用的異步返回類型:

從 C# 7.0 開始,異步方法可返回任何具有可訪問的 GetAwaiter 方法的類型。

ValueTask<TResult>

Task 和 Task<TResult> 是引用類型,因此,性能關鍵路徑中的內存分配會對性能產生負面影響,尤其當分配出現在緊湊循環中時。 支持通用返回類型意味着可返回輕量值類型(而不是引用類型),從而避免額外的內存分配。

使用ValueTask<TResult>,需要添加NuGet包 System.Threading.Tasks.Extensions

ValueTask<TResult> 是struct值類型,Task 和 Task<TResult> 是class引用類型

異步操作的生命週期

Task 類提供了異步操作的生命週期,且該週期由 TaskStatus 枚舉表示。

狀態 執行順序 備註
Created 0 該任務已初始化,但尚未安排。
WaitingForActivation 1 該任務正在等待被.NET Framework infrastructure 內部激活和調度。
WaitingToRun 2 該任務已安排執行但尚未開始執行。
Running 3 任務正在運行但尚未完成。
WaitingForChildrenToComplete 4 任務已完成執行,並隱式等待附加的子任務完成。
RanToCompletion 5 任務已成功完成執行。
Canceled 6 引發 OperationCanceledException 異常,或者在任務開始執行之前取消
Faulted 7 由於未處理的異常,任務已完成。

Canceled 和 Faulted狀態都會因爲任務異常導致轉換爲該狀態。二者的區別如下:

如果標記的 IsCancellationRequested 屬性返回 false,或者異常的標記與任務的標記不匹配,則會將 OperationCanceledException 按照普通的異常來處理,從而導致任務轉換爲 Faulted 狀態。 另外還要注意,其他異常的存在將也會導致任務轉換爲 Faulted 狀態。 您可以在 Status 屬性中獲取已完成任務的狀態。

參考文章:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章