C#使用Task實(shí)現(xiàn)執(zhí)行并行任務(wù)的原理的示例詳解
一、Task執(zhí)行并行任務(wù)的原理
使用Task執(zhí)行并行任務(wù)的原理是將任務(wù)分成多個(gè)小塊,每個(gè)小塊都可以在不同的線程上運(yùn)行。然后,使用Task.Run方法將這些小塊作為不同的任務(wù)提交給線程池。線程池會(huì)自動(dòng)管理線程的創(chuàng)建和銷毀,并根據(jù)系統(tǒng)資源的可用情況來(lái)自動(dòng)調(diào)整線程數(shù)量,從而實(shí)現(xiàn)最大化利用CPU資源的效果。
二、5個(gè)示例展示
示例1
下面是一個(gè)簡(jiǎn)單的示例,展示如何使用Task來(lái)執(zhí)行并行任務(wù):
void?Task1() { ????//?創(chuàng)建任務(wù)數(shù)組 ????var?tasks?=?new?Task[10]; ????for?(var?i?=?0;?i?<?tasks.Length;?i++) ????{ ????????var?taskId?=?i?+?1; ????????//?使用Task.Run方法提交任務(wù) ????????tasks[i]?=?Task.Run(()?=> ????????{ ????????????Console.WriteLine("任務(wù)?{0}?運(yùn)行在線程?{1}?中",?taskId,?Task.CurrentId); ????????????//?執(zhí)行任務(wù)邏輯 ????????}); ????} ????//?等待所有任務(wù)完成 ????Task.WaitAll(tasks); ????Console.WriteLine("所有任務(wù)運(yùn)行完成。"); ????Console.ReadKey(); }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)長(zhǎng)度為10的任務(wù)數(shù)組,然后使用Task.Run方法將每個(gè)任務(wù)提交給線程池。在任務(wù)執(zhí)行時(shí),使用Task.CurrentId屬性獲取當(dāng)前任務(wù)的ID,并打印出來(lái)以方便觀察。最后,我們使用Task.WaitAll方法等待所有任務(wù)完成并打印出一條完成信息。
運(yùn)行的結(jié)果:
任務(wù) 3 運(yùn)行在線程 11 中
任務(wù) 4 運(yùn)行在線程 12 中
任務(wù) 8 運(yùn)行在線程 16 中
任務(wù) 1 運(yùn)行在線程 9 中
任務(wù) 2 運(yùn)行在線程 10 中
任務(wù) 5 運(yùn)行在線程 13 中
任務(wù) 6 運(yùn)行在線程 14 中
任務(wù) 7 運(yùn)行在線程 15 中
任務(wù) 9 運(yùn)行在線程 17 中
任務(wù) 10 運(yùn)行在線程 18 中
所有任務(wù)運(yùn)行完成。
值得注意的是,在實(shí)際開(kāi)發(fā)中,需要根據(jù)具體情況來(lái)評(píng)估任務(wù)的大小和數(shù)量,以確保并行任務(wù)的效率和可靠性。
示例2
另一個(gè)使用Task的示例是計(jì)算斐波那契數(shù)列。我們可以將斐波那契數(shù)列的每一項(xiàng)看成一個(gè)任務(wù),然后使用Task.WaitAll方法等待所有任務(wù)完成。
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;?//?計(jì)算斐波那契數(shù)列的前n項(xiàng) ????var?tasks?=?new?Task<long>[n]; ????for?(var?i?=?0;?i?<?n;?i++) ????{ ????????var?index?=?i;?//?需要在閉包內(nèi)使用循環(huán)變量時(shí)需要賦值給另外一個(gè)變量 ????????if?(i?<?2) ????????{ ????????????tasks[i]?=?Task.FromResult((long)i); ????????} ????????else ????????{ ????????????tasks[i]?=?Task.Run(()?=>?Fib(index)); ????????} ????} ????//?等待所有任務(wù)完成 ????Task.WaitAll(tasks); ????//?打印結(jié)果 ????for?(var?i?=?0;?i?<?n;?i++) ????{ ????????Console.Write("{0}?",?tasks[i].Result); ????} ????Console.ReadKey(); }
在這個(gè)示例中,我們使用Task數(shù)組來(lái)存儲(chǔ)所有的任務(wù)。如果需要計(jì)算的是前兩項(xiàng),則直接使用Task.FromResult創(chuàng)建完成任務(wù),否則使用Task.Run方法創(chuàng)建異步任務(wù)并調(diào)用Fib方法計(jì)算結(jié)果。在等待所有任務(wù)完成后,我們遍歷Task數(shù)組,并使用Task.Result屬性獲取每個(gè)任務(wù)的結(jié)果并打印出來(lái)。
運(yùn)行的結(jié)果:
0 1 1 2 3 5 8 13 21 34
需要注意的是,在創(chuàng)建異步任務(wù)時(shí),由于循環(huán)變量在閉包內(nèi)的值是不確定的,因此需要將其賦值給另外一個(gè)變量,并在閉包內(nèi)使用該變量。否則,所有任務(wù)可能會(huì)使用同一個(gè)循環(huán)變量的值,導(dǎo)致結(jié)果錯(cuò)誤。
示例3
除了使用Task數(shù)組存儲(chǔ)所有任務(wù),還可以使用Task.Factory.StartNew方法創(chuàng)建并行任務(wù)。這個(gè)方法與Task.Run方法類似,都可以創(chuàng)建異步任務(wù)并提交給線程池。
void?Task3() { ????long?Factorial(int?n) ????{ ????????if?(n?==?0)?return?1; ????????return?n?*?Factorial(n?-?1); ????} ????const?int?n?=?5;?//?計(jì)算階乘的數(shù) ????var?task?=?Task.Factory.StartNew(()?=>?Factorial(n)); ????Console.WriteLine("計(jì)算階乘..."); ????//?等待任務(wù)完成 ????task.Wait(); ????Console.WriteLine("{0}!?=?{1}",?n,?task.Result); ????Console.ReadKey(); }
在這個(gè)示例中,我們使用Task.Factory.StartNew方法創(chuàng)建一個(gè)計(jì)算階乘的異步任務(wù),并等待任務(wù)完成后打印結(jié)果。
運(yùn)行結(jié)果:
計(jì)算階乘...
5! = 120
需要注意的是,盡管Task.Run和Task.Factory.StartNew方法都可以創(chuàng)建異步任務(wù),但它們的行為略有不同。特別是,Task.Run方法總是使用TaskScheduler.Default作為任務(wù)調(diào)度器,而Task.Factory.StartNew方法可以指定任務(wù)調(diào)度器、任務(wù)類型和其他選項(xiàng)。因此,在選擇使用哪種方法時(shí),需要根據(jù)具體情況進(jìn)行評(píng)估。
示例4
另一個(gè)使用Task的示例是異步讀取文件。在這個(gè)示例中,我們使用Task.FromResult方法創(chuàng)建一個(gè)完成任務(wù),并將文件內(nèi)容作為結(jié)果返回。
void?Task4() { ????const?string?filePath?=?"test.txt"; ????var?task?=?Task.FromResult(File.ReadAllText(filePath));?//?只是方便舉例,更好的代碼應(yīng)該是:File.ReadAllTextAsync(filePath);? ????Console.WriteLine("讀取文件內(nèi)容..."); ????//?等待任務(wù)完成 ????task.Wait(); ????Console.WriteLine("文件內(nèi)容:?{0}",?task.Result); ????Console.ReadKey(); }
在這個(gè)示例中,我們使用Task.FromResult方法創(chuàng)建一個(gè)完成任務(wù),并通過(guò)File.ReadAllText方法讀取文件內(nèi)容并將其作為結(jié)果返回。在等待任務(wù)完成后,我們可以通過(guò)調(diào)用Task.Result屬性來(lái)獲取任務(wù)的結(jié)果。
文中記事本請(qǐng)隨意創(chuàng)建
需要注意的是,在實(shí)際開(kāi)發(fā)中,如果需要處理大型文件或需要執(zhí)行長(zhǎng)時(shí)間的I/O操作,則應(yīng)該使用異步代碼來(lái)避免阻塞UI線程。例如,在讀取大型文件時(shí),我們可以使用異步代碼來(lái)避免阻塞UI線程,從而提高應(yīng)用程序的性能和響應(yīng)速度。
示例5
最后一個(gè)示例是使用Task和async/await實(shí)現(xiàn)異步任務(wù)。在這個(gè)示例中,我們將一個(gè)耗時(shí)的操作封裝為異步方法,并使用async/await關(guān)鍵字來(lái)等待該操作完成。
async?Task?Task5() { ????async?Task<string>?LongOperationAsync() ????{ ????????//?模擬耗時(shí)操作 ????????await?Task.Delay(TimeSpan.FromSeconds(3)); ????????return?"完成"; ????} ????Console.WriteLine($"{DateTime.Now:yyyy-MM-dd?HH:mm:ss.fff}開(kāi)始耗時(shí)操作..."); ????//?等待異步方法完成 ????var?result?=?await?LongOperationAsync(); ????Console.WriteLine($"{DateTime.Now:yyyy-MM-dd?HH:mm:ss.fff}耗時(shí)操作完成:?{result}"); ????Console.ReadKey(); }
在這個(gè)示例中,我們使用async/await關(guān)鍵字將LongOperationAsync方法聲明為異步方法,并使用await關(guān)鍵字等待Task.Delay操作完成。在主程序中,我們可以使用await關(guān)鍵字等待LongOperationAsync完成并獲取其結(jié)果。
2023-03-28 20:54:09.111開(kāi)始耗時(shí)操作...
2023-03-28 20:54:12.143耗時(shí)操作完成: 完成
需要注意的是,在使用async/await關(guān)鍵字時(shí),應(yīng)該避免在異步方法內(nèi)部使用阻塞線程的操作,否則可能會(huì)導(dǎo)致UI線程被阻塞。如果必須執(zhí)行阻塞操作,可以將其放在不同的線程上執(zhí)行,或者使用異步IO操作來(lái)避免阻塞線程。
三、使用async/await關(guān)鍵字注意
在使用async/await關(guān)鍵字時(shí),還有一些細(xì)節(jié)需要注意,再給出兩個(gè)示例。
示例1
示例代碼如下所示:
async?Task?Task6() { ????async?Task<string>?LongOperationAsync(int?id) ????{ ????????//?模擬耗時(shí)操作 ????????await?Task.Delay(TimeSpan.FromSeconds(1?+?id)); ????????return?$"{DateTime.Now:ss.fff}完成?{id}"; ????} ????Console.WriteLine($"{DateTime.Now:yyyy-MM-dd?HH:mm:ss.fff}開(kāi)始耗時(shí)操作..."); ????//?等待多個(gè)異步任務(wù)完成 ????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}耗時(shí)操作完成:?{resultStr}"); ????Console.ReadKey(); }
在這個(gè)示例中,我們使用Task.WhenAll方法等待多個(gè)異步任務(wù)完成,并使用Join方法將所有任務(wù)的結(jié)果連接起來(lái)作為最終結(jié)果。
2023-03-28 21:15:42.855開(kāi)始耗時(shí)操作...
2023-03-28 21:15:46.894耗時(shí)操作完成: 44.888完成 1,45.883完成 2,46.893完成 3
示例2
另一個(gè)需要注意的問(wèn)題是,在使用async/await
關(guān)鍵字時(shí),應(yīng)該盡可能避免使用ConfigureAwait(false)
方法。這個(gè)方法可以讓異步操作不必恢復(fù)到原始的SynchronizationContext
上,從而減少線程切換的開(kāi)銷和提高性能。
然而,在某些情況下,如果在異步操作完成后需要返回到原始的SynchronizationContext
上,使用ConfigureAwait(false)
會(huì)導(dǎo)致調(diào)用者無(wú)法正確處理結(jié)果。因此,建議僅在確定不需要返回到原始的SynchronizationContext
上時(shí)才使用ConfigureAwait(false)
方法。
示例代碼: 假設(shè)我們有一個(gè)控制臺(tái)應(yīng)用程序,其中有兩個(gè)異步方法:MethodAAsync()和MethodBAsync()。MethodAAsync()會(huì)等待1秒鐘,然后返回一個(gè)字符串。MethodBAsync()會(huì)等待2秒鐘,然后返回一個(gè)字符串。代碼如下所示:
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)在,我們想要同時(shí)調(diào)用這兩個(gè)方法,并將它們的結(jié)果合并成一個(gè)字符串。我們可以像下面這樣編寫代碼:
async?Task<string>?CombineResultsAAsync() { ????var?resultA?=?await?MethodAAsync(); ????var?resultB?=?await?MethodBAsync(); ????return?$"{DateTime.Now:yyyy-MM-dd?HH:mm:ss.fff}:?{resultA}?|?{resultB}"; }
這個(gè)代碼看起來(lái)非常簡(jiǎn)單明了,但是它存在一個(gè)性能問(wèn)題。當(dāng)我們調(diào)用CombineResultsAAsync()方法時(shí),第一個(gè)await操作將使執(zhí)行上下文切換回原始SynchronizationContext(即主線程),因此我們的異步操作將在UI線程上運(yùn)行。由于我們要等待1秒鐘才能從MethodAAsync()中返回結(jié)果,因此UI線程將被阻塞,直到異步操作完成并且結(jié)果可用為止。
這種情況下,我們可以使用ConfigureAwait(false)方法來(lái)指定不需要保留當(dāng)前上下文的線程執(zhí)行狀態(tài),從而讓異步操作在一個(gè)線程池線程上運(yùn)行。這可以通過(guò)下面的代碼實(shí)現(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}"; }
通過(guò)使用ConfigureAwait(false)方法,我們告訴異步操作不需要保留當(dāng)前上下文的線程執(zhí)行狀態(tài),這樣異步操作就會(huì)在一個(gè)線程池線程上運(yùn)行,而不是在UI線程上運(yùn)行。這樣做可以避免一些潛在的性能問(wèn)題,因?yàn)槲覀兊腢I線程不會(huì)被阻塞,并且異步操作可以在一個(gè)新的線程池線程上運(yùn)行。
四、總結(jié)
在使用async/await關(guān)鍵字時(shí),應(yīng)該遵循一些最佳實(shí)踐,以提高代碼的可讀性、可維護(hù)性和性能。下面是一些常見(jiàn)的最佳實(shí)踐:
- 盡可能將異步方法聲明為
Task
或Task<TResult>
類型,以便可以使用await關(guān)鍵字等待其完成。如果異步方法不返回任何內(nèi)容,則應(yīng)將其聲明為Task類型。 - 在異步方法內(nèi)部盡可能避免使用阻塞線程的操作,而應(yīng)該使用非阻塞操作來(lái)模擬延遲。如果必須執(zhí)行阻塞操作,可以將其放在不同的線程上執(zhí)行,或者使用異步IO操作來(lái)避免阻塞線程。
- 在異步方法內(nèi)部不要捕獲異常并立即處理,因?yàn)檫@會(huì)導(dǎo)致代碼變得復(fù)雜難以維護(hù)。應(yīng)該讓調(diào)用者自行處理異常。如果必須在異步方法內(nèi)部捕獲異常,也應(yīng)該將其包裝成
AggregateException
異常,并將其傳遞給調(diào)用者。 - 在使用
ConfigureAwait(false)
方法時(shí)要小心,只有在確定不需要返回到原始的SynchronizationContext
上時(shí)才使用,否則可能會(huì)導(dǎo)致調(diào)用者無(wú)法正確處理結(jié)果。 - 盡量避免在異步方法中使用不安全的線程API,例如
Thread.Sleep
或Thread.Join
等方法,以確保代碼的可移植性和穩(wěn)定性。應(yīng)該使用非阻塞的異步方法來(lái)模擬延遲。 - 在使用async/await關(guān)鍵字時(shí),應(yīng)該遵循一些命名約定,例如異步方法的名稱應(yīng)該以
Async
結(jié)尾,以便于區(qū)分同步和異步方法。 - 在需要同時(shí)等待多個(gè)異步任務(wù)完成時(shí),可以使用
Task.WhenAll
方法等待所有任務(wù)完成。如果只需要等待其中一個(gè)任務(wù)完成,則可以使用Task.WhenAny
方法等待任意一個(gè)任務(wù)完成。 - 在異步方法內(nèi)部,應(yīng)該將耗時(shí)的操作封裝為另外的異步方法,并在需要的地方使用
async/await
關(guān)鍵字調(diào)用它們,以提高代碼的可讀性和可維護(hù)性。 - 在使用async/await關(guān)鍵字時(shí),應(yīng)該盡可能避免使用線程同步機(jī)制,例如
lock
關(guān)鍵字或Monitor
類,因?yàn)檫@會(huì)導(dǎo)致UI線程被阻塞。而應(yīng)該使用異步鎖或其他非阻塞的線程同步機(jī)制。
總之,使用Task和async/await可以大大簡(jiǎn)化異步編程,提高代碼的可讀性、可維護(hù)性和性能。但是,需要注意一些細(xì)節(jié)和最佳實(shí)踐,以確保代碼的正確性和穩(wěn)定性。
到此這篇關(guān)于C#使用Task實(shí)現(xiàn)執(zhí)行并行任務(wù)的原理的示例詳解的文章就介紹到這了,更多相關(guān)C# Task執(zhí)行并行任務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#使用前序遍歷、中序遍歷和后序遍歷打印二叉樹(shù)的方法
這篇文章主要介紹了C#使用前序遍歷、中序遍歷和后序遍歷打印二叉樹(shù)的方法,涉及C#遍歷二叉樹(shù)的相關(guān)技巧,需要的朋友可以參考下2015-04-04C# 無(wú)需COM組件創(chuàng)建快捷方式的實(shí)現(xiàn)代碼
做一個(gè)小程序, 需要?jiǎng)?chuàng)建快捷方式, 網(wǎng)上普遍的做法是引入 COM 組件, 雖然也挺方便的, 但引入之后, 程序就需要多帶一個(gè) dll 文件, 這樣, 想做成單文件便攜版就不行了2011-05-05C#實(shí)現(xiàn)一個(gè)Word保護(hù)性模板文件
這篇文章主要為大家詳細(xì)介紹了C#如何實(shí)現(xiàn)一個(gè)Word保護(hù)性模板文件,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的小伙伴可以參考一下2024-01-01C#實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器功能(窗體)
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01C#一個(gè)簡(jiǎn)單的定時(shí)小程序?qū)崿F(xiàn)代碼
這篇文章主要介紹了C#實(shí)現(xiàn)一個(gè)簡(jiǎn)單的定時(shí)小程序代碼,實(shí)現(xiàn)過(guò)程很簡(jiǎn)單,需要的朋友可以參考下2015-09-09