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

C#使用Task實現(xiàn)執(zhí)行并行任務的原理的示例詳解

 更新時間:2023年04月04日 11:41:11   作者:Dotnet9  
Task是一個表示異步操作的類,它提供了一種簡單、輕量級的方式來創(chuàng)建多線程應用程序。本文就來和大家聊聊在C#中如何使用Task執(zhí)行并行任務吧

一、Task執(zhí)行并行任務的原理

使用Task執(zhí)行并行任務的原理是將任務分成多個小塊,每個小塊都可以在不同的線程上運行。然后,使用Task.Run方法將這些小塊作為不同的任務提交給線程池。線程池會自動管理線程的創(chuàng)建和銷毀,并根據(jù)系統(tǒng)資源的可用情況來自動調(diào)整線程數(shù)量,從而實現(xiàn)最大化利用CPU資源的效果。

二、5個示例展示

示例1

下面是一個簡單的示例,展示如何使用Task來執(zhí)行并行任務:

void?Task1()
{
????//?創(chuàng)建任務數(shù)組
????var?tasks?=?new?Task[10];

????for?(var?i?=?0;?i?<?tasks.Length;?i++)
????{
????????var?taskId?=?i?+?1;

????????//?使用Task.Run方法提交任務
????????tasks[i]?=?Task.Run(()?=>
????????{
????????????Console.WriteLine("任務?{0}?運行在線程?{1}?中",?taskId,?Task.CurrentId);
????????????//?執(zhí)行任務邏輯
????????});
????}

????//?等待所有任務完成
????Task.WaitAll(tasks);

????Console.WriteLine("所有任務運行完成。");
????Console.ReadKey();
}

在這個示例中,我們創(chuàng)建了一個長度為10的任務數(shù)組,然后使用Task.Run方法將每個任務提交給線程池。在任務執(zhí)行時,使用Task.CurrentId屬性獲取當前任務的ID,并打印出來以方便觀察。最后,我們使用Task.WaitAll方法等待所有任務完成并打印出一條完成信息。

運行的結果:

任務 3 運行在線程 11 中
任務 4 運行在線程 12 中
任務 8 運行在線程 16 中
任務 1 運行在線程 9 中
任務 2 運行在線程 10 中
任務 5 運行在線程 13 中
任務 6 運行在線程 14 中
任務 7 運行在線程 15 中
任務 9 運行在線程 17 中
任務 10 運行在線程 18 中
所有任務運行完成。

值得注意的是,在實際開發(fā)中,需要根據(jù)具體情況來評估任務的大小和數(shù)量,以確保并行任務的效率和可靠性。

示例2

另一個使用Task的示例是計算斐波那契數(shù)列。我們可以將斐波那契數(shù)列的每一項看成一個任務,然后使用Task.WaitAll方法等待所有任務完成。

void?Task2()
{
????static?long?Fib(int?n)
????{
????????if?(n?is?0?or?1)
????????{
????????????return?n;
????????}
????????else
????????{
????????????return?Fib(n?-?1)?+?Fib(n?-?2);
????????}
????}

????const?int?n?=?10;?//?計算斐波那契數(shù)列的前n項

????var?tasks?=?new?Task<long>[n];

????for?(var?i?=?0;?i?<?n;?i++)
????{
????????var?index?=?i;?//?需要在閉包內(nèi)使用循環(huán)變量時需要賦值給另外一個變量

????????if?(i?<?2)
????????{
????????????tasks[i]?=?Task.FromResult((long)i);
????????}
????????else
????????{
????????????tasks[i]?=?Task.Run(()?=>?Fib(index));
????????}
????}

????//?等待所有任務完成
????Task.WaitAll(tasks);

????//?打印結果
????for?(var?i?=?0;?i?<?n;?i++)
????{
????????Console.Write("{0}?",?tasks[i].Result);
????}

????Console.ReadKey();
}

在這個示例中,我們使用Task數(shù)組來存儲所有的任務。如果需要計算的是前兩項,則直接使用Task.FromResult創(chuàng)建完成任務,否則使用Task.Run方法創(chuàng)建異步任務并調(diào)用Fib方法計算結果。在等待所有任務完成后,我們遍歷Task數(shù)組,并使用Task.Result屬性獲取每個任務的結果并打印出來。

運行的結果:

0 1 1 2 3 5 8 13 21 34

需要注意的是,在創(chuàng)建異步任務時,由于循環(huán)變量在閉包內(nèi)的值是不確定的,因此需要將其賦值給另外一個變量,并在閉包內(nèi)使用該變量。否則,所有任務可能會使用同一個循環(huán)變量的值,導致結果錯誤。

示例3

除了使用Task數(shù)組存儲所有任務,還可以使用Task.Factory.StartNew方法創(chuàng)建并行任務。這個方法與Task.Run方法類似,都可以創(chuàng)建異步任務并提交給線程池。

void?Task3()
{
????long?Factorial(int?n)
????{
????????if?(n?==?0)?return?1;
????????return?n?*?Factorial(n?-?1);
????}

????const?int?n?=?5;?//?計算階乘的數(shù)

????var?task?=?Task.Factory.StartNew(()?=>?Factorial(n));

????Console.WriteLine("計算階乘...");

????//?等待任務完成
????task.Wait();

????Console.WriteLine("{0}!?=?{1}",?n,?task.Result);
????Console.ReadKey();
}

在這個示例中,我們使用Task.Factory.StartNew方法創(chuàng)建一個計算階乘的異步任務,并等待任務完成后打印結果。

運行結果:

計算階乘...
5! = 120

需要注意的是,盡管Task.Run和Task.Factory.StartNew方法都可以創(chuàng)建異步任務,但它們的行為略有不同。特別是,Task.Run方法總是使用TaskScheduler.Default作為任務調(diào)度器,而Task.Factory.StartNew方法可以指定任務調(diào)度器、任務類型和其他選項。因此,在選擇使用哪種方法時,需要根據(jù)具體情況進行評估。

示例4

另一個使用Task的示例是異步讀取文件。在這個示例中,我們使用Task.FromResult方法創(chuàng)建一個完成任務,并將文件內(nèi)容作為結果返回。

void?Task4()
{
????const?string?filePath?=?"test.txt";

????var?task?=?Task.FromResult(File.ReadAllText(filePath));?//?只是方便舉例,更好的代碼應該是:File.ReadAllTextAsync(filePath);?

????Console.WriteLine("讀取文件內(nèi)容...");

????//?等待任務完成
????task.Wait();

????Console.WriteLine("文件內(nèi)容:?{0}",?task.Result);
????Console.ReadKey();
}

在這個示例中,我們使用Task.FromResult方法創(chuàng)建一個完成任務,并通過File.ReadAllText方法讀取文件內(nèi)容并將其作為結果返回。在等待任務完成后,我們可以通過調(diào)用Task.Result屬性來獲取任務的結果。

文中記事本請隨意創(chuàng)建

需要注意的是,在實際開發(fā)中,如果需要處理大型文件或需要執(zhí)行長時間的I/O操作,則應該使用異步代碼來避免阻塞UI線程。例如,在讀取大型文件時,我們可以使用異步代碼來避免阻塞UI線程,從而提高應用程序的性能和響應速度。

示例5

最后一個示例是使用Task和async/await實現(xiàn)異步任務。在這個示例中,我們將一個耗時的操作封裝為異步方法,并使用async/await關鍵字來等待該操作完成。

async?Task?Task5()
{
????async?Task<string>?LongOperationAsync()
????{
????????//?模擬耗時操作
????????await?Task.Delay(TimeSpan.FromSeconds(3));

????????return?"完成";
????}

????Console.WriteLine($"{DateTime.Now:yyyy-MM-dd?HH:mm:ss.fff}開始耗時操作...");

????//?等待異步方法完成
????var?result?=?await?LongOperationAsync();

????Console.WriteLine($"{DateTime.Now:yyyy-MM-dd?HH:mm:ss.fff}耗時操作完成:?{result}");
????Console.ReadKey();
}

在這個示例中,我們使用async/await關鍵字將LongOperationAsync方法聲明為異步方法,并使用await關鍵字等待Task.Delay操作完成。在主程序中,我們可以使用await關鍵字等待LongOperationAsync完成并獲取其結果。

2023-03-28 20:54:09.111開始耗時操作...
2023-03-28 20:54:12.143耗時操作完成: 完成

需要注意的是,在使用async/await關鍵字時,應該避免在異步方法內(nèi)部使用阻塞線程的操作,否則可能會導致UI線程被阻塞。如果必須執(zhí)行阻塞操作,可以將其放在不同的線程上執(zhí)行,或者使用異步IO操作來避免阻塞線程。

三、使用async/await關鍵字注意

在使用async/await關鍵字時,還有一些細節(jié)需要注意,再給出兩個示例。

示例1

示例代碼如下所示:

async?Task?Task6()
{
????async?Task<string>?LongOperationAsync(int?id)
????{
????????//?模擬耗時操作
????????await?Task.Delay(TimeSpan.FromSeconds(1?+?id));

????????return?$"{DateTime.Now:ss.fff}完成?{id}";
????}

????Console.WriteLine($"{DateTime.Now:yyyy-MM-dd?HH:mm:ss.fff}開始耗時操作...");

????//?等待多個異步任務完成
????var?task1?=?LongOperationAsync(1);
????var?task2?=?LongOperationAsync(2);
????var?task3?=?LongOperationAsync(3);

????var?results?=?await?Task.WhenAll(task1,?task2,?task3);
????var?resultStr?=?string.Join(",",?results);

????Console.WriteLine($"{DateTime.Now:yyyy-MM-dd?HH:mm:ss.fff}耗時操作完成:?{resultStr}");
????Console.ReadKey();
}

在這個示例中,我們使用Task.WhenAll方法等待多個異步任務完成,并使用Join方法將所有任務的結果連接起來作為最終結果。

2023-03-28 21:15:42.855開始耗時操作...
2023-03-28 21:15:46.894耗時操作完成: 44.888完成 1,45.883完成 2,46.893完成 3

示例2

另一個需要注意的問題是,在使用async/await關鍵字時,應該盡可能避免使用ConfigureAwait(false)方法。這個方法可以讓異步操作不必恢復到原始的SynchronizationContext上,從而減少線程切換的開銷和提高性能。

然而,在某些情況下,如果在異步操作完成后需要返回到原始的SynchronizationContext上,使用ConfigureAwait(false)會導致調(diào)用者無法正確處理結果。因此,建議僅在確定不需要返回到原始的SynchronizationContext上時才使用ConfigureAwait(false)方法。

示例代碼: 假設我們有一個控制臺應用程序,其中有兩個異步方法:MethodAAsync()和MethodBAsync()。MethodAAsync()會等待1秒鐘,然后返回一個字符串。MethodBAsync()會等待2秒鐘,然后返回一個字符串。代碼如下所示:

async?Task<string>?MethodAAsync()
{
????await?Task.Delay(1000);
????return?$"{DateTime.Now:ss.fff}>Hello";
}

async?Task<string>?MethodBAsync()
{
????await?Task.Delay(2000);
????return?$"{DateTime.Now:ss.fff}>World";
}

現(xiàn)在,我們想要同時調(diào)用這兩個方法,并將它們的結果合并成一個字符串。我們可以像下面這樣編寫代碼:

async?Task<string>?CombineResultsAAsync()
{
????var?resultA?=?await?MethodAAsync();
????var?resultB?=?await?MethodBAsync();
????return?$"{DateTime.Now:yyyy-MM-dd?HH:mm:ss.fff}:?{resultA}?|?{resultB}";
}

這個代碼看起來非常簡單明了,但是它存在一個性能問題。當我們調(diào)用CombineResultsAAsync()方法時,第一個await操作將使執(zhí)行上下文切換回原始SynchronizationContext(即主線程),因此我們的異步操作將在UI線程上運行。由于我們要等待1秒鐘才能從MethodAAsync()中返回結果,因此UI線程將被阻塞,直到異步操作完成并且結果可用為止。

這種情況下,我們可以使用ConfigureAwait(false)方法來指定不需要保留當前上下文的線程執(zhí)行狀態(tài),從而讓異步操作在一個線程池線程上運行。這可以通過下面的代碼實現(xiàn):

async?Task<string>?CombineResultsBAsync()
{
????var?resultA?=?await?MethodAAsync().ConfigureAwait(false);
????var?resultB?=?await?MethodBAsync().ConfigureAwait(false);
????return?$"{DateTime.Now:yyyy-MM-dd?HH:mm:ss.fff}:?{resultA}?|?{resultB}";
}

通過使用ConfigureAwait(false)方法,我們告訴異步操作不需要保留當前上下文的線程執(zhí)行狀態(tài),這樣異步操作就會在一個線程池線程上運行,而不是在UI線程上運行。這樣做可以避免一些潛在的性能問題,因為我們的UI線程不會被阻塞,并且異步操作可以在一個新的線程池線程上運行。

四、總結

在使用async/await關鍵字時,應該遵循一些最佳實踐,以提高代碼的可讀性、可維護性和性能。下面是一些常見的最佳實踐:

  • 盡可能將異步方法聲明為TaskTask<TResult>類型,以便可以使用await關鍵字等待其完成。如果異步方法不返回任何內(nèi)容,則應將其聲明為Task類型。
  • 在異步方法內(nèi)部盡可能避免使用阻塞線程的操作,而應該使用非阻塞操作來模擬延遲。如果必須執(zhí)行阻塞操作,可以將其放在不同的線程上執(zhí)行,或者使用異步IO操作來避免阻塞線程。
  • 在異步方法內(nèi)部不要捕獲異常并立即處理,因為這會導致代碼變得復雜難以維護。應該讓調(diào)用者自行處理異常。如果必須在異步方法內(nèi)部捕獲異常,也應該將其包裝成AggregateException異常,并將其傳遞給調(diào)用者。
  • 在使用ConfigureAwait(false)方法時要小心,只有在確定不需要返回到原始的SynchronizationContext上時才使用,否則可能會導致調(diào)用者無法正確處理結果。
  • 盡量避免在異步方法中使用不安全的線程API,例如Thread.SleepThread.Join等方法,以確保代碼的可移植性和穩(wěn)定性。應該使用非阻塞的異步方法來模擬延遲。
  • 在使用async/await關鍵字時,應該遵循一些命名約定,例如異步方法的名稱應該以Async結尾,以便于區(qū)分同步和異步方法。
  • 在需要同時等待多個異步任務完成時,可以使用Task.WhenAll方法等待所有任務完成。如果只需要等待其中一個任務完成,則可以使用Task.WhenAny方法等待任意一個任務完成。
  • 在異步方法內(nèi)部,應該將耗時的操作封裝為另外的異步方法,并在需要的地方使用async/await關鍵字調(diào)用它們,以提高代碼的可讀性和可維護性。
  • 在使用async/await關鍵字時,應該盡可能避免使用線程同步機制,例如lock關鍵字或Monitor類,因為這會導致UI線程被阻塞。而應該使用異步鎖或其他非阻塞的線程同步機制。

總之,使用Task和async/await可以大大簡化異步編程,提高代碼的可讀性、可維護性和性能。但是,需要注意一些細節(jié)和最佳實踐,以確保代碼的正確性和穩(wěn)定性。

到此這篇關于C#使用Task實現(xiàn)執(zhí)行并行任務的原理的示例詳解的文章就介紹到這了,更多相關C# Task執(zhí)行并行任務內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論