C 5.0 推出async和await,最早是.NET Framework 4.5引入,可以在Visual Studio 2012使用。在此之前的非同步編程實現難度較高,async使非同步編程的實現變得簡便。 各平臺對async的支持情況 |平臺|async| | | | |.NET 4.5及以上|& ...
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 屬性中獲取已完成任務的狀態。
參考文章: