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.Wait
或Task<T>.Result
方法,因爲它們會導致死鎖。如果使用了async
,最好就一直使用它。 - 異步方法的參數不能使用
out
,ref
。out
或ref
返回的數據應借用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 的異步方法的調用方無法捕獲從該方法引發的異常,且此類未經處理的異常可能會導致應用程序故障。 如果返回
Task
或Task<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 屬性中獲取已完成任務的狀態。
參考文章: