最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#并發(fā)編程之a(chǎn)sync和await關(guān)鍵字詳解

 更新時間:2023年07月25日 10:23:51   作者:橙子家  
對于?async?和?await?兩個關(guān)鍵字,對于一線開發(fā)人員再熟悉不過了,到處都是它們的身影,下面小編就來和大家記錄匯總下它們的使用吧

〇、前言

對于 async 和 await 兩個關(guān)鍵字,對于一線開發(fā)人員再熟悉不過了,到處都是它們的身影。

從 C# 5.0 時代引入 async 和 await 關(guān)鍵字,我們使用 async 修飾符可將方法、lambda 表達式或匿名方法指定為異步。 如果對方法或表達式使用此修飾符,則其稱為異步方法。async 和 await 通過與 .NET Framework 4.0 時引入的任務(wù)并行庫(TPL:Task Parallel Library)構(gòu)成了新的異步編程模型,即 TAP(基于任務(wù)的異步模式 Task-based asynchronous pattern)。

但是如果對他們不太了解的話,會有很多麻煩出現(xiàn),所以最近查了一些資料,也看了幾個大佬的介紹,今天來記錄匯總下。

一、先通過一個簡單的示例來互相認識下

如下代碼,在 Main 方法中,調(diào)用一個異步方法,因為 Main 本身不支持 async,所以不能直接使用 await 關(guān)鍵字來完成異步等待等操作。

static void Main(string[] args) // 由于 Main 方法不支持 async,所以只能通過 AsyncTask() 來調(diào)用異步方法
{
    Console.WriteLine("--開始!");
    Console.WriteLine($"--下面我(主線程)先通知下兒子(子線程)也開始。 我的 ID:{Thread.CurrentThread.ManagedThreadId}");
    // 調(diào)用 async 修飾的方法,也就是異步執(zhí)行的方法
    AsyncTask(); // 異步方法,不占用主線程,是另新創(chuàng)建的新的子線程
    Console.WriteLine("--我(主線程)已經(jīng)讓我兒子(子線程)開始工作了,我也繼續(xù)工作");
    Console.WriteLine($"--我(主線程)完成! 我的 ID:{Thread.CurrentThread.ManagedThreadId}");
    Console.ReadLine();
}
// async 修飾的方法,也就是異步方法,不占用主線程
public static async Task AsyncTask()
{
    Thread.Sleep(1000);
    Console.WriteLine($"--我剛到,還沒找到兒子(子線程)的房間,我的 ID:{Thread.CurrentThread.ManagedThreadId}");
    var result = await WasteTime(); // 主線程遇到 await,是不會等待的,直接繼續(xù)執(zhí)行,接下來的事情交給子線程
    Console.WriteLine(result);
    Console.WriteLine($"兒子(子線程)已經(jīng)干完了應(yīng)該干的事情! 我的 ID:{Thread.CurrentThread.ManagedThreadId}");
}
// async 修飾的方法,也就是異步方法,不占用主線程
private static async Task<string> WasteTime()
{
    Console.WriteLine($"--我終于找到了,下面準備讓兒子(子線程)開干!我的 ID:{Thread.CurrentThread.ManagedThreadId}");
    return await Task.Run(() => // 創(chuàng)建一個子線程
    {
        Console.WriteLine($"兒子(子線程)開始異步執(zhí)行了! 我的 ID:{Thread.CurrentThread.ManagedThreadId}");
        // 模擬耗時操作
        Thread.Sleep(5000);
        return $"兒子(子線程)異步執(zhí)行完了。我的 ID:{Thread.CurrentThread.ManagedThreadId}";
    });
}

如下結(jié)果輸出,加了雙橫杠--的是主線程的輸出:

二、關(guān)于 async 關(guān)鍵字

使用 async 修飾符可將方法、lambda 表達式或匿名方法指定為異步,此時 async 稱為關(guān)鍵字,其他所有上下文中都解釋為標(biāo)識符。如果對方法或表達式使用此修飾符,則其稱為異步方法。如下代碼,定義一個異步方法 ExampleMethodAsync():

public async Task<int> ExampleMethodAsync()
{
    //...
}

異步方法同步運行,直至到達其第一個 await 表達式,此時會將方法掛起,直到等待的任務(wù)完成。

如果 async 關(guān)鍵字修改的方法不包含 await 表達式或語句,則該方法將同步執(zhí)行。編譯器警告將通知你不包含 await 語句的任何異步方法,因為該情況可能表示存在錯誤。警告信息如下圖: 

異步方法可具有以下返回類型:

  • Task
  • Task<TResult>
  • void。 對于除事件處理程序以外的代碼,通常不鼓勵使用 async void 方法,因為調(diào)用方不能 await 那些方法,并且必須實現(xiàn)不同的機制來報告成功完成或錯誤條件。
  • 任何具有可訪問的 GetAwaiter 方法的類型。 System.Threading.Tasks.ValueTask<TResult> 類型屬于此類實現(xiàn)。 它通過添加 NuGet 包 System.Threading.Tasks.Extensions 的方式可用。

此異步方法既不能聲明任何 in、ref 或 out 參數(shù),也不能具有引用返回值,但它可以調(diào)用具有此類參數(shù)的方法。

三、關(guān)于 await 關(guān)鍵字

3.1 await 的用法示例

await 運算符(異步等待任務(wù)完成)可以讓主線程,跳過對其所修飾的 async 方法的執(zhí)行等待,將耗時操作交給子線程,從而完成異步操作。異步操作完成后,await 運算符將返回操作的結(jié)果(如果有)。

當(dāng) await 運算符用到表示已完成操作的異步方法時,它將立即返回操作的結(jié)果,類似于同步執(zhí)行。

await 運算符不會阻止計算異步方法的線程。當(dāng) await 運算符占用子線程執(zhí)行其異步方法時,主線程將返回到原執(zhí)行路徑上繼續(xù)往下執(zhí)行。

如下代碼,兩個 async 修飾的異步方法:

  • 首先在【1】位置調(diào)用異步方法DownloadDocsMainPageAsync(),由于這里沒有 await 運算符,所以按照同步方式運行,進入到方法體內(nèi)部,到達【2】位置。
  • 在【2】位置,代碼中通過在異步方法GetByteArrayAsync()前加了 await 運算符,預(yù)示著這里將進行異步操作,創(chuàng)建新的線程,然后釋放主線程,繼續(xù)回Main()函數(shù)中往下運行。
  • 由于【2】這一行代碼是耗時操作,因此主線程執(zhí)行到【3】位置,這里有出現(xiàn)了 await 運算符,指的是等待異步線程的結(jié)果,此時主線程就下線了,接下來就是子線程的表演時間了。
  • 最后子線程下載操作完成,返回到【3】位置,完成其余的工作。
public static async Task Main()
{
    Task<int> downloading = DownloadDocsMainPageAsync(); // 【1】
    Console.WriteLine($"{nameof(Main)}: 啟動下載。。。ThreadID:{Thread.CurrentThread.ManagedThreadId}");
    int bytesLoaded = await downloading; // 【3】
    Console.WriteLine($"{nameof(Main)}: 共下載了 {bytesLoaded} bytes。ThreadID:{Thread.CurrentThread.ManagedThreadId}");
    Console.ReadLine();
}
private static async Task<int> DownloadDocsMainPageAsync()
{
    Console.WriteLine($"{nameof(DownloadDocsMainPageAsync)}: 即將開始下載。ThreadID:{Thread.CurrentThread.ManagedThreadId}");
    var client = new HttpClient();
    byte[] content = await client.GetByteArrayAsync("https://learn.microsoft.com/en-us/"); // 【2】
    Console.WriteLine($"{nameof(DownloadDocsMainPageAsync)}: 完成下載。ThreadID:{Thread.CurrentThread.ManagedThreadId}");
    return content.Length;
}

輸出結(jié)果如下圖:

代碼實際執(zhí)行的流程大概畫下:

3.2 await foreach() 示例

可以通過 await foreach 語句來使用異步數(shù)據(jù)流,即實現(xiàn) IAsyncEnumerable<T> 接口的集合類型。異步檢索下一個元素時,可能會掛起循環(huán)的每次迭代。

public class Program
{
    static async Task Main(string[] args)
    {
        const int count = 5;
        ConsoleExt.WriteLineAsync($"-------------------1開始示例異步測試");
        //ConsoleExt.WriteLineAsync($"-------------------2開始示例異步測試");
        //ConsoleExt.WriteLineAsync($"-------------------3開始示例異步測試");
        // 創(chuàng)建一個新的任務(wù),用于【生成】異步序列數(shù)據(jù)
        IAsyncEnumerable<int> pullBasedAsyncSequence = ProduceAsyncSumSeqeunc(count);
        // 創(chuàng)建一個新的任務(wù),用于【使用】異步序列數(shù)據(jù)
        var consumingTask = Task.Run(() => ConsumeAsyncSumSeqeunc(pullBasedAsyncSequence));
        ConsoleExt.WriteLineAsync($"-------------------開始做其他耗時操作");
        await Task.Delay(TimeSpan.FromSeconds(3)); // 模擬耗時操作
        ConsoleExt.WriteLineAsync($"-------------------結(jié)束做其他耗時操作");
        await consumingTask; // 等待異步任務(wù)完成
        ConsoleExt.WriteLineAsync($"-------------------結(jié)束示例異步測試");
        Console.ReadLine();
    }
    static async Task ConsumeAsyncSumSeqeunc(IAsyncEnumerable<int> sequence) // 使用
    {
        ConsoleExt.WriteLineAsync($"ConsumeAsyncSumSeqeunc 被調(diào)用");
        await foreach (var value in sequence)
        {
            ConsoleExt.WriteLineAsync($"----接收延遲返回的值 {value}");
            await Task.Delay(TimeSpan.FromSeconds(1)); // 模擬耗時操作
        };
    }
    private static async IAsyncEnumerable<int> ProduceAsyncSumSeqeunc(int count) // 生成
    {
        ConsoleExt.WriteLineAsync($"ProduceAsyncSumSeqeunc 被調(diào)用");
        var sum = 0;
        for (var i = 0; i <= count; i++)
        {
            sum = sum + i;
            await Task.Delay(TimeSpan.FromSeconds(0.5)); // 模擬耗時操作
            ConsoleExt.WriteLineAsync($"ProduceAsyncSumSeqeunc 返回 sum:{sum}");
            yield return sum; // yield 關(guān)鍵字表示延遲加載,將全部返回值一個一個返回
        }
    }
}
public static class ConsoleExt
{
    public static void WriteLine(object message)
    {
        Console.WriteLine($"(Time: {DateTime.Now.ToString("HH:mm:ss.ffffff")},  Thread {Thread.CurrentThread.ManagedThreadId}): {message} ");
    }
    public static async void WriteLineAsync(object message)
    {
        await Task.Run(() => Console.WriteLine($"(Time: {DateTime.Now.ToString("HH:mm:ss.ffffff")},  Thread {Thread.CurrentThread.ManagedThreadId}): {message} "));
    }
}

輸出結(jié)果如下圖,特別關(guān)注一下線程 12,它不僅在 foreach 迭代中執(zhí)行任務(wù),而且還抽空把Main()方法中的也執(zhí)行了,這樣就極大的發(fā)揮了多線程的好處,任務(wù)操作安排的滿滿的,避免浪費資源。

常規(guī)的 foreach() 方法,是單線程的,后一個操作必須在前一個操作完成后開始,這樣對于多邏輯處理器的機器來說,就像是宰牛刀對付小雞兒了。

詳情可參考:聊一聊C# 8.0中的await foreach

3.3 關(guān)于 await using()

可以說 await using() 的使用是和 IAsyncDisposable 接口息息相關(guān)的。

IAsyncDisposable 接口,提供一種用于異步釋放非托管資源的機制。與之對應(yīng)的就是提供同步釋放非托管資源機制的接口 IDisposable。提供此類及時釋放機制,可使用戶執(zhí)行資源密集型釋放操作,從而無需長時間占用 GUI 應(yīng)用程序的主線程。同時更好的完善.NET異步編程的體驗,IAsyncDisposable誕生了。

現(xiàn)在 .NET 的很多類庫都已經(jīng)同時支持了 IDisposable 和 IAsyncDisposable。而從使用者的角度來看,其實調(diào)用任何一個釋放方法都能夠達到釋放資源的目的。就好比 DbContext 的 SaveChanges和 SaveChangesAsync。但是從未來的發(fā)展角度來看,IAsyncDisposable 會成使用的更加頻繁。因為它應(yīng)該能夠優(yōu)雅地處理托管資源,而不必擔(dān)心死鎖。而對于現(xiàn)在已有代碼中實現(xiàn)了 IDisposable 的類,如果想要使用 IAsyncDisposable。建議您同時實現(xiàn)兩個接口,已保證使用者在使用時,無論調(diào)用哪個接口都能達到效果,而達到兼容性的目的。

如下示例代碼繼承了 IAsyncDisposable 接口,然后就可以使用 await using 語法了:

// 【前提】先實現(xiàn)接口 IAsyncDisposable
public class ExampleClass : IAsyncDisposable
{
	private Stream _memoryStream = new MemoryStream();
	public ExampleClass()
	{	}
	public async ValueTask DisposeAsync()
	{
		await _memoryStream.DisposeAsync();
	}
}
// 【第一種】然后就可以使用 using 語法糖
await using var s = new ExampleClass()
{
	// 具體操作。。。
};
// 【第二種】優(yōu)化 同樣是對象 s 只存在于當(dāng)前代碼塊
await using var s = new ExampleClass();
// 具體操作。。。

詳情可參考:熟悉而陌生的新朋友——IAsyncDisposable

四、await Task 和 Task.GetAwaiter()

4.1 關(guān)于 Task.GetAwaiter()

最常用的等待異步線程完成的修飾符就是 await,那么如果不用它怎么判斷任務(wù)執(zhí)行情況呢?這時候 Task.GetAwaiter() 就上場了。

如下代碼,task.GetAwaiter().OnCompleted(() => { })的目的就是在 task 執(zhí)行狀態(tài)為 RunToCompletion 時執(zhí)行其中的匿名函數(shù)。

class Program
{
    static void Main()
    {
        var task = Task.Run(() => {
            return GetName();
        });
        task.GetAwaiter().OnCompleted(() => {
            var name = task.Result;
            ConsoleExt.WriteLine("獲取到的名稱為:" + name);
        });
        ConsoleExt.WriteLine("主線程執(zhí)行完畢");
        Console.ReadLine();
    }
    static string GetName()
    {
        ConsoleExt.WriteLine("另外一個線程在獲取名稱");
        Thread.Sleep(2000);
        return "GetName--名稱";
    }
}
public static class ConsoleExt
{
    public static void WriteLine(object message)
    {
        Console.WriteLine($"(Time: {DateTime.Now.ToString("HH:mm:ss.ffffff")},  Thread {Thread.CurrentThread.ManagedThreadId}): {message} ");
    }
    public static async void WriteLineAsync(object message)
    {
        await Task.Run(() => Console.WriteLine($"(Time: {DateTime.Now.ToString("HH:mm:ss.ffffff")},  Thread {Thread.CurrentThread.ManagedThreadId}): {message} "));
    }
}

如下輸出結(jié)果,1 為主線程,4 為子線程:

4.2 await Task 和 Task.GetAwaiter() 的區(qū)別

在異步返回的 Task 實例前加上 await 關(guān)鍵字之后,后面的代碼會被掛起等待,直到 task 執(zhí)行完畢有返回值的時候才會繼續(xù)向下執(zhí)行,這一段時間主線程會處于掛起狀態(tài)。例如本文 3.1 await 的用法示例 中的示例,總共下載了多少內(nèi)容在最后才被輸出。

GetAwaiter() 方法則會返回一個 awaitable 的對象(繼承了 INotifyCompletion.OnCompleted 方法),通過public void OnCompleted(Action continuation)方法,我們只是傳遞了一個委托(Action)進去,等 task 完成了就會執(zhí)行這個委托,但是并不會影響主線程,下面的代碼會立即執(zhí)行。這也是為什么我們在本文上一章節(jié) 4.1 關(guān)于 Task.GetAwaiter() 的輸出結(jié)果里面,“主線程執(zhí)行完畢”寫在最后,而非最后輸出的原因。

那么我們通過 GetAwaiter() 方法如何能達到 await Task 的效果呢?

// GetResult() 方法就是阻塞線程,直到 task 執(zhí)行完成,返回結(jié)果 name
var name = task.GetAwaiter().GetResult();
// 上邊這行的效果,等同于
var name = await task;

await 實質(zhì)是在調(diào)用 awaitable 對象的 GetResult() 方法。

以上就是C#并發(fā)編程之a(chǎn)sync和await關(guān)鍵字詳解的詳細內(nèi)容,更多關(guān)于C# async await的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 基于WinForm+Halcon實現(xiàn)圖像縮放與交互功能

    基于WinForm+Halcon實現(xiàn)圖像縮放與交互功能

    本文主要講述在 WinForm 中結(jié)合 Halcon 實現(xiàn)圖像縮放、平移及實時顯示灰度值等交互功能,包括初始化窗口的不同方式,以及通過特定事件添加相應(yīng)功能的詳細步驟和代碼示例,能提升圖像處理應(yīng)用的開發(fā)效率和用戶體驗,需要的朋友可以參考下
    2025-01-01
  • C#防SQL注入代碼的三種方法

    C#防SQL注入代碼的三種方法

    這篇文章主要介紹了C#防SQL注入代碼的三種方法,有需要的朋友可以參考一下
    2014-01-01
  • 三十分鐘快速掌握C# 6.0知識點

    三十分鐘快速掌握C# 6.0知識點

    這篇文章主要介紹了C# 6.0的相關(guān)知識點,文中介紹的非常詳細,通過這篇文字可以讓大家在三十分鐘內(nèi)快速的掌握C# 6.0,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-03-03
  • 使用C#代碼在PDF中添加或刪除附件

    使用C#代碼在PDF中添加或刪除附件

    除文本和圖形外,PDF 文件還可以包含作為附件嵌入的完整文件,這使得成套文檔的交換更加方便和可靠,本文演示如何使用 Spire.PDF for .NET 在 C# 和 VB.NET 中向 PDF 文檔添加或刪除這兩種類型的附件,需要的朋友可以參考下
    2025-12-12
  • C#實現(xiàn)字符串首字母大寫的方法示例

    C#實現(xiàn)字符串首字母大寫的方法示例

    這篇文章主要給大家介紹了關(guān)于利用C#實現(xiàn)字符串首字母大寫的相關(guān)資料,這是在最近工作中遇到的一個需求,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-01-01
  • C#基于UDP實現(xiàn)的P2P語音聊天工具

    C#基于UDP實現(xiàn)的P2P語音聊天工具

    這篇文章主要是一個應(yīng)用,使用udp傳送語音和文本等信息。在這個系統(tǒng)中沒有服務(wù)端和客戶端,相互通訊都是直接相互聯(lián)系的,能夠很好的實現(xiàn)效果
    2015-09-09
  • C#文件操作類分享

    C#文件操作類分享

    這篇文章主要為大家分享了C#文件操作類的相關(guān)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • C#使用DeepSeek?API實現(xiàn)自然語言處理,文本分類和情感分析

    C#使用DeepSeek?API實現(xiàn)自然語言處理,文本分類和情感分析

    在C#中使用DeepSeek?API可以實現(xiàn)多種功能,例如自然語言處理、文本分類、情感分析等,本文主要為大家介紹了具體實現(xiàn)步驟,需要的可以了解下
    2025-02-02
  • 基于C# WinForms開發(fā)的Windows系統(tǒng)監(jiān)控工具

    基于C# WinForms開發(fā)的Windows系統(tǒng)監(jiān)控工具

    最近開發(fā)了一個輕量級的 Windows 系統(tǒng)監(jiān)控工具,可以實時監(jiān)控 CPU、內(nèi)存、GPU、溫度、網(wǎng)絡(luò)流量等硬件信息,并通過曲線圖表直觀展示,本文分享一下開發(fā)過程中的核心技術(shù)點和關(guān)鍵代碼實現(xiàn),需要的朋友可以參考下
    2025-12-12
  • 基于C#制作考試答題系統(tǒng)

    基于C#制作考試答題系統(tǒng)

    這篇文章主要為大家詳細介紹了如何利用C#制作帶窗體的考試答題系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03

最新評論

贡觉县| 沙湾县| 湖北省| 蒙城县| 巴中市| 昌乐县| 彭州市| 青冈县| 利川市| 太康县| 荆门市| 峨山| 乡宁县| 遂溪县| 五莲县| 英山县| 武汉市| 鄂伦春自治旗| 牟定县| 临洮县| 都匀市| 潞城市| 平遥县| 汕头市| 德惠市| 金沙县| 绥化市| 西充县| 固镇县| 博乐市| 长泰县| 漾濞| 新乡县| 陵川县| 南郑县| 社旗县| 张北县| 泰州市| 郧西县| 易门县| 海伦市|