1. 程式人生 > >C#中await/async閒說

C#中await/async閒說

自從C#5.0增加非同步程式設計之後,非同步程式設計越來越簡單,async和await用的地方越來越多,越來越好用,只要用非同步的地方都是一連串的非同步,如果想要非同步程式設計的時候,需要從底層開始編寫,這樣後邊使用的時候就是非同步,那麼底層是如何實現??我們如何編寫高效率的非同步方法??

#瞭解基於任務的非同步模式(TAP)

基於任務的非同步程式設計模型 (TAP) 提供了非同步程式碼的抽象化,你只需像往常一樣將程式碼編寫為一連串語句即可,在開始呼叫的地方執行。例如:var task = method()①; await task②; 在①的時候開始執行可能還沒有執行完,在②程式掛起等待執行完,中間怎麼執行的你不需要知道,編譯器會做若干操作的。當開啟多個任務的時候,像要他們都執行完,在執行其他的時候,可以await Task.WhenAll(task1,task2 .....);

#瞭解async/await

await 運算子應用於非同步方法,在方法的執行中插入掛起點,直到所等待任務完成。使用async 和await定義非同步方法不一定會建立新執行緒,當編譯器看到await關鍵字時,執行緒會掛起等待執行結束。
await 僅可用於由 async 關鍵字修改的非同步方法中,使用 async 修飾符定義的方法通常包含一個或多個 await 表示式,使用await運算子的任務通常是實現[基於任務的非同步模式(TAP)]的方法呼叫返回,返回值包括 Task、Task<TResult>、ValueTask 和 ValueTask<TResult> 物件的方法。

# 呼叫 Task.Wait() 或者 Task.Result 立刻產生死鎖的充分條件

1. 呼叫 Wait() 或 Result 的程式碼位於 UI 執行緒。
2. Task 的實際執行在其他執行緒,且需要返回 UI 執行緒。
死鎖的原因:UWP、WPF、Windows Forms 程式的 UI 執行緒都是單執行緒的。為了避免產生死鎖,你應該一條道走到黑, Async All the Way。或者.ConfigureAwait(false)

# ValueTask與Task的區別

7.0為async新增的ValueTask的作用(如果沒有在Nuget上下載System.Threading.Tasks.Extensions,ValueTask就在這個庫中),ValueTask用於值型別的非同步;Task為引用型別的,每次需要分配空間。

例如:

public async Task<int> CalculateSum(int a, int b) {
    if (a == 0 && b == 0)
    {
        return 0;
    }

    return await Task.Run(() => a + b);
}

當a,b=0的時候不會執行到task裡,這個時候返回task就造成了資源的浪費,修改為以下會效率更高

public async ValueTask<int> CalculateSum2(int a, int b)
{
    if (a == 0 && b == 0)
    {
        return 0;
    }

    return await Task.Run(() => a + b);
}

但是也不是說到處用ValueTask會好,當是引用型別的時候,用ValueTask,你需要關注更多的資料,這個時候用Task會更好。

# await/async原理分析

[AsyncStateMachine(typeof(Class1.<CalculateSum2>d__1))]
public ValueTask<int> CalculateSum2(int a, int b)
{
    Class1.<CalculateSum2>d__1 <CalculateSum2>d__;
    <CalculateSum2>d__.a = a;
    <CalculateSum2>d__.b = b;
    <CalculateSum2>d__.<>t__builder = AsyncValueTaskMethodBuilder<int>.Create();
    <CalculateSum2>d__.<>1__state = -1;
    AsyncValueTaskMethodBuilder<int> <>t__builder = <CalculateSum2>d__.<>t__builder;
    <>t__builder.Start<Class1.<CalculateSum2>d__1>(ref <CalculateSum2>d__);
    return <CalculateSum2>d__.<>t__builder.Task;
}

對CalculateSum2程式碼解析,發現沒有await/async,原來又是編譯器提供的語法糖。

[__DynamicallyInvokable, DebuggerStepThrough, SecuritySafeCritical]
public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
    if (stateMachine == null)
    {
        throw new ArgumentNullException("stateMachine");
    }
    ExecutionContextSwitcher executionContextSwitcher = default(ExecutionContextSwitcher);
    RuntimeHelpers.PrepareConstrainedRegions();
    try
    {
        ExecutionContext.EstablishCopyOnWriteScope(ref executionContextSwitcher);
        stateMachine.MoveNext();
    }
    finally
    {
        executionContextSwitcher.Undo();
    }
}

對Start方法進行分析,可以看出MoveNext,程式的執行其實還是一步一步進行的,那麼await/async會不會建立一個執行緒,這倒是不一定,這個由執行緒池決定,那麼非同步了不建立一個執行緒,怎麼非同步的,這裡的非同步可能是執行在已經有的執行緒