欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

〇、前言

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

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

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

一、先通過(guò)一個(gè)簡(jiǎn)單的示例來(lái)互相認(rèn)識(shí)下

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

static void Main(string[] args) // 由于 Main 方法不支持 async,所以只能通過(guò) AsyncTask() 來(lái)調(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,是不會(huì)等待的,直接繼續(xù)執(zhí)行,接下來(lái)的事情交給子線程
    Console.WriteLine(result);
    Console.WriteLine($"兒子(子線程)已經(jīng)干完了應(yīng)該干的事情! 我的 ID:{Thread.CurrentThread.ManagedThreadId}");
}
// async 修飾的方法,也就是異步方法,不占用主線程
private static async Task<string> WasteTime()
{
    Console.WriteLine($"--我終于找到了,下面準(zhǔn)備讓兒子(子線程)開干!我的 ID:{Thread.CurrentThread.ManagedThreadId}");
    return await Task.Run(() => // 創(chuàng)建一個(gè)子線程
    {
        Console.WriteLine($"兒子(子線程)開始異步執(zhí)行了! 我的 ID:{Thread.CurrentThread.ManagedThreadId}");
        // 模擬耗時(shí)操作
        Thread.Sleep(5000);
        return $"兒子(子線程)異步執(zhí)行完了。我的 ID:{Thread.CurrentThread.ManagedThreadId}";
    });
}

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

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

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

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

異步方法同步運(yùn)行,直至到達(dá)其第一個(gè) await 表達(dá)式,此時(shí)會(huì)將方法掛起,直到等待的任務(wù)完成。

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

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

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

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

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

3.1 await 的用法示例

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

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

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

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

  • 首先在【1】位置調(diào)用異步方法DownloadDocsMainPageAsync(),由于這里沒有 await 運(yùn)算符,所以按照同步方式運(yùn)行,進(jìn)入到方法體內(nèi)部,到達(dá)【2】位置。
  • 在【2】位置,代碼中通過(guò)在異步方法GetByteArrayAsync()前加了 await 運(yùn)算符,預(yù)示著這里將進(jìn)行異步操作,創(chuàng)建新的線程,然后釋放主線程,繼續(xù)回Main()函數(shù)中往下運(yùn)行。
  • 由于【2】這一行代碼是耗時(shí)操作,因此主線程執(zhí)行到【3】位置,這里有出現(xiàn)了 await 運(yùn)算符,指的是等待異步線程的結(jié)果,此時(shí)主線程就下線了,接下來(lái)就是子線程的表演時(shí)間了。
  • 最后子線程下載操作完成,返回到【3】位置,完成其余的工作。
public static async Task Main()
{
    Task<int> downloading = DownloadDocsMainPageAsync(); // 【1】
    Console.WriteLine($"{nameof(Main)}: 啟動(dòng)下載。。。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é)果如下圖:

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

3.2 await foreach() 示例

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

public class Program
{
    static async Task Main(string[] args)
    {
        const int count = 5;
        ConsoleExt.WriteLineAsync($"-------------------1開始示例異步測(cè)試");
        //ConsoleExt.WriteLineAsync($"-------------------2開始示例異步測(cè)試");
        //ConsoleExt.WriteLineAsync($"-------------------3開始示例異步測(cè)試");
        // 創(chuàng)建一個(gè)新的任務(wù),用于【生成】異步序列數(shù)據(jù)
        IAsyncEnumerable<int> pullBasedAsyncSequence = ProduceAsyncSumSeqeunc(count);
        // 創(chuàng)建一個(gè)新的任務(wù),用于【使用】異步序列數(shù)據(jù)
        var consumingTask = Task.Run(() => ConsumeAsyncSumSeqeunc(pullBasedAsyncSequence));
        ConsoleExt.WriteLineAsync($"-------------------開始做其他耗時(shí)操作");
        await Task.Delay(TimeSpan.FromSeconds(3)); // 模擬耗時(shí)操作
        ConsoleExt.WriteLineAsync($"-------------------結(jié)束做其他耗時(shí)操作");
        await consumingTask; // 等待異步任務(wù)完成
        ConsoleExt.WriteLineAsync($"-------------------結(jié)束示例異步測(cè)試");
        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)); // 模擬耗時(shí)操作
        };
    }
    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)); // 模擬耗時(shí)操作
            ConsoleExt.WriteLineAsync($"ProduceAsyncSumSeqeunc 返回 sum:{sum}");
            yield return sum; // yield 關(guān)鍵字表示延遲加載,將全部返回值一個(gè)一個(gè)返回
        }
    }
}
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ù)操作安排的滿滿的,避免浪費(fèi)資源。

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

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

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

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

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

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

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

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

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

四、await Task 和 Task.GetAwaiter()

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

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

如下代碼,task.GetAwaiter().OnCompleted(() => { })的目的就是在 task 執(zhí)行狀態(tài)為 RunToCompletion 時(shí)執(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("另外一個(gè)線程在獲取名稱");
        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 實(shí)例前加上 await 關(guān)鍵字之后,后面的代碼會(huì)被掛起等待,直到 task 執(zhí)行完畢有返回值的時(shí)候才會(huì)繼續(xù)向下執(zhí)行,這一段時(shí)間主線程會(huì)處于掛起狀態(tài)。例如本文 3.1 await 的用法示例 中的示例,總共下載了多少內(nèi)容在最后才被輸出。

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

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

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

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

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

相關(guān)文章

最新評(píng)論