C# 異步編程

若是須要 I/O 綁定(例如從網絡請求數據或訪問數據庫),則須要利用異步編程。 還可使用 CPU 綁定代碼(例如執行成本高昂的計算),對編寫異步代碼而言,這是一個不錯的方案。web

異步模型的基本概述

異步編程的核心是 Task 和 Task<T> 對象,這兩個對象對異步操做建模。 它們受關鍵字 async 和 await 的支持。 在大多數狀況下模型十分簡單:數據庫

對於 I/O 綁定代碼,當你 await 一個操做,它將返回 async 方法中的一個 Task 或 Task<T>編程

對於 CPU 綁定代碼,當你 await 一個操做,它將在後臺線程經過 Task.Run 方法啓動。網絡

 

識別 CPU 綁定和 I/O 綁定工做

編寫代碼前應考慮的兩個問題:併發

1.你的代碼是否會「等待」某些內容,例如數據庫中的數據?app

若是答案爲「是」,則你的工做是 I/O 綁定。異步

2.你的代碼是否要執行開銷巨大的計算?async

若是答案爲「是」,則你的工做是 CPU 綁定。異步編程

若是你的工做爲 I/O 綁定,請使用 async 和 await(而不使用 Task.Run)。 不該使用任務並行庫。 相關緣由在深刻了解異步的文章中說明。spa

若是你的工做爲 CPU 綁定,而且你重視響應能力,請使用 async 和 await,並在另外一個線程上使用 Task.Run 生成工做。 若是該工做同時適用於併發和並行,則應考慮使用任務並行庫

 

I/O 綁定示例:從 Web 服務下載數據

你可能須要在按下按鈕時從 Web 服務下載某些數據,但不但願阻止 UI 線程。 只需執行以下操做便可輕鬆實現:

private readonly HttpClient _httpClient = new HttpClient();

downloadButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI as the request
    // from the web service is happening.
    //
    // The UI thread is now free to perform other work.
    var stringData = await _httpClient.GetStringAsync(URL);
    DoSomethingWithData(stringData);
};

CPU 綁定示例:爲遊戲執行計算

假設你正在編寫一個移動遊戲,在該遊戲中,按下某個按鈕將會對屏幕中的許多敵人形成傷害。 執行傷害計算的開銷可能極大,並且在 UI 線程中執行計算有可能使遊戲在計算執行過程當中暫停!

此問題的最佳解決方法是啓動一個後臺線程,它使用 Task.Run 執行工做,並 await 其結果。 這可確保在執行工做時 UI 能流暢運行。

private DamageResult CalculateDamageDone()
{
    // Code omitted:
    //
    // Does an expensive calculation and returns
    // the result of that calculation.
}


calculateButton.Clicked += async (o, e) =>
{
    // This line will yield control to the UI while CalculateDamageDone()
    // performs its work.  The UI thread is free to perform other work.
    var damageResult = await Task.Run(() => CalculateDamageDone());
    DisplayDamage(damageResult);
};

它無需手動管理後臺線程,而是經過非阻止性的方式來實現。

等待多個任務完成

此示例演示如何爲一組 User 捕捉 userId 數據。

public async Task<User> GetUserAsync(int userId)
{
    // Code omitted:
    //
    // Given a user Id {userId}, retrieves a User object corresponding
    // to the entry in the database with {userId} as its Id.
}

public static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds)
{
    var getUserTasks = new List<Task<User>>();

    foreach (int userId in userIds)
    {
        getUserTasks.Add(GetUserAsync(userId));
    }

    return await Task.WhenAll(getUserTasks);
}

如下是使用 LINQ 進行更簡潔編寫的另外一種方法:

public async Task<User> GetUserAsync(int userId)
{
    // Code omitted:
    //
    // Given a user Id {userId}, retrieves a User object corresponding
    // to the entry in the database with {userId} as its Id.
}

public static async Task<User[]> GetUsersAsync(IEnumerable<int> userIds)
{
    var getUserTasks = userIds.Select(id => GetUserAsync(id));
    return await Task.WhenAll(getUserTasks);
}

儘管它的代碼較少,但在混合 LINQ 和異步代碼時須要謹慎操做。 由於 LINQ 使用延遲的執行,所以異步調用將不會像在 foreach()循環中那樣馬上發生,除非強制所生成的序列經過對 .ToList() 或 .ToArray() 的調用循環訪問。

LINQ 中的 Lambda 表達式使用延遲執行,這意味着代碼可能在你並不但願結束的時候中止執行。 若是編寫不正確,將阻塞任務引入其中時可能很容易致使死鎖。 此外,此類異步代碼嵌套可能會對推斷代碼的執行帶來更多困難。 Async 和 LINQ 的功能都十分強大,但在結合使用二者時應儘量當心。

相關文章
相關標籤/搜索