C#中異步與多線程的作用有哪些?針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。
創(chuàng)新互聯(lián)公司堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:網(wǎng)站制作、成都做網(wǎng)站、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的安鄉(xiāng)網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!UI線程
還有一件重要的事情需要知道的是為什么使用這些工具是好的。在.net中,有一個(gè)主線程叫做UI線程,它負(fù)責(zé)更新屏幕的所有可視部分。默認(rèn)情況下,這是一切運(yùn)行的地方。當(dāng)你點(diǎn)擊一個(gè)按鈕,你想看到按鈕被短暫地按下,然后返回,這是UI線程的責(zé)任。你的應(yīng)用中只有一個(gè)UI線程,這意味著如果你的UI線程忙著做繁重的計(jì)算或等待網(wǎng)絡(luò)請(qǐng)求之類的事情,那么它不能更新你在屏幕上看到的東西,直到它完成。結(jié)果是,你的應(yīng)用程序看起來(lái)像“凍結(jié)”——你可以點(diǎn)擊一個(gè)按鈕,但似乎什么都不會(huì)發(fā)生,因?yàn)閁I線程正在忙著做其他事情。
理想情況下,你希望UI線程盡可能地空閑,這樣你的應(yīng)用程序似乎總是在響應(yīng)用戶的操作。這就是異步和多線程的由來(lái)。通過(guò)使用這些工具,可以確保在其他地方完成繁重的工作,UI線程保持良好和響應(yīng)性。
現(xiàn)在讓我們看看如何在c#中使用這些工具。
執(zhí)行異步操作的代碼非常簡(jiǎn)單。你應(yīng)該知道兩個(gè)主要的關(guān)鍵字:“async”和“await”,所以人們通常將其稱為async/await。假設(shè)你現(xiàn)在有這樣的代碼:
public void Loopy() { var hugeFiles = new string[] { "Gr8Gonzos_Home_Movie_In_8k_Res.mkv", // 1 GB "War_And_Peace_In_150_Languages.rtf", // 1.2 GB "Cats_On_Catnip.mpg" // 0.9 GB }; foreach (var hugeFile in hugeFiles) { ReadAHugeFile(hugeFile); } MessageBox.Show("All done!"); } public byte[] ReadAHugeFile(string bigFile) { var fileSize = new FileInfo(bigFile).Length; // Get the file size var allData = new byte[fileSize]; // Allocate a byte array as large as our file using (var fs = new System.IO.FileStream(bigFile, FileMode.Open)) { fs.Read(allData, 0, (int)fileSize); // Read the entire file... } return allData; // ...and return those bytes! }
在當(dāng)前的形式中,這些都是同步運(yùn)行的。如果你點(diǎn)擊一個(gè)按鈕從UI線程運(yùn)行Loopy(),那么應(yīng)用程序?qū)⑺坪鮾鼋Y(jié),直到所有三大文件閱讀,因?yàn)槊總€(gè)“ReadAHugeFile”是要花很長(zhǎng)時(shí)間在UI線程上運(yùn)行,并將同步閱讀。這可不好!讓我們看看能否將ReadAHugeFile變?yōu)楫惒降倪@樣UI線程就能繼續(xù)處理其他東西。
無(wú)論何時(shí),只要有支持異步的命令,微軟通常會(huì)給我們同步和異步版本的這些命令。在上面的代碼中,System.IO.FileStream對(duì)象同時(shí)具有"Read"和"ReadAsync"方法。所以第一步就是將“fs.Read”修改成“fs.ReadAsync”。
public byte[] ReadAHugeFile(string bigFile) { var fileSize = new FileInfo(bigFile).Length; // Get the file size var allData = new byte[fileSize]; // Allocate a byte array as large as our file using (var fs = new System.IO.FileStream(bigFile, FileMode.Open)) { fs.ReadAsync(allData, 0, (int)fileSize); // Read the entire file asynchronously... } return allData; // ...and return those bytes! }
如果現(xiàn)在運(yùn)行它,它會(huì)立即返回,并且“allData”字節(jié)數(shù)組中不會(huì)有任何數(shù)據(jù)。為什么?
這是因?yàn)镽eadAsync是開始讀取并返回一個(gè)任務(wù)對(duì)象,這有點(diǎn)像一個(gè)書簽。這是.net的一個(gè)“Promise”,一旦異步活動(dòng)完成(例如從硬盤讀取數(shù)據(jù)),它將返回結(jié)果,任務(wù)對(duì)象可以用來(lái)訪問(wèn)結(jié)果。但如果我們對(duì)這個(gè)任務(wù)不做任何事情,那么系統(tǒng)就會(huì)立即繼續(xù)到下一行代碼,也就是我們的"return allData"行,它會(huì)返回一個(gè)尚未填滿數(shù)據(jù)的數(shù)組。
因此,告訴代碼等待結(jié)果是很有用的(但這樣一來(lái),原始線程可以在此期間繼續(xù)做其他事情)。為了做到這一點(diǎn),我們使用了一個(gè)"awaiter",它就像在async調(diào)用之前添加單詞"await"一樣簡(jiǎn)單:
public byte[] ReadAHugeFile(string bigFile) { var fileSize = new FileInfo(bigFile).Length; // Get the file size var allData = new byte[fileSize]; // Allocate a byte array as large as our file using (var fs = new System.IO.FileStream(bigFile, FileMode.Open)) { await fs.ReadAsync(allData, 0, (int)fileSize); // Read the entire file asynchronously... } return allData; // ...and return those bytes! }
如果現(xiàn)在運(yùn)行它,它會(huì)立即返回,并且“allData”字節(jié)數(shù)組中不會(huì)有任何數(shù)據(jù)。為什么?
這是因?yàn)镽eadAsync是開始讀取并返回一個(gè)任務(wù)對(duì)象,這有點(diǎn)像一個(gè)書簽。這是.net的一個(gè)“Promise”,一旦異步活動(dòng)完成(例如從硬盤讀取數(shù)據(jù)),它將返回結(jié)果,任務(wù)對(duì)象可以用來(lái)訪問(wèn)結(jié)果。但如果我們對(duì)這個(gè)任務(wù)不做任何事情,那么系統(tǒng)就會(huì)立即繼續(xù)到下一行代碼,也就是我們的"return allData"行,它會(huì)返回一個(gè)尚未填滿數(shù)據(jù)的數(shù)組。
因此,告訴代碼等待結(jié)果是很有用的(但這樣一來(lái),原始線程可以在此期間繼續(xù)做其他事情)。為了做到這一點(diǎn),我們使用了一個(gè)"awaiter",它就像在async調(diào)用之前添加單詞"await"一樣簡(jiǎn)單:
public byte[] ReadAHugeFile(string bigFile) { var fileSize = new FileInfo(bigFile).Length; // Get the file size var allData = new byte[fileSize]; // Allocate a byte array as large as our file using (var fs = new System.IO.FileStream(bigFile, FileMode.Open)) { await fs.ReadAsync(allData, 0, (int)fileSize); // Read the entire file asynchronously... } return allData; // ...and return those bytes! }
哦。如果你試過(guò),你會(huì)發(fā)現(xiàn)有一個(gè)錯(cuò)誤。這是因?yàn)?net需要知道這個(gè)方法是異步的,它最終會(huì)返回一個(gè)字節(jié)數(shù)組。因此,我們做的第一件事是在返回類型之前添加單詞“async”,然后用Task<…>,是這樣的:
public async TaskReadAHugeFile(string bigFile) { var fileSize = new FileInfo(bigFile).Length; // Get the file size var allData = new byte[fileSize]; // Allocate a byte array as large as our file using (var fs = new System.IO.FileStream(bigFile, FileMode.Open)) { await fs.ReadAsync(allData, 0, (int)fileSize); // Read the entire file asynchronously... } return allData; // ...and return those bytes! }
好吧!現(xiàn)在我們烹飪!如果我們現(xiàn)在運(yùn)行我們的代碼,它將繼續(xù)在UI線程上運(yùn)行,直到我們到達(dá)ReadAsync方法的await。此時(shí),. net知道這是一個(gè)將由硬盤執(zhí)行的活動(dòng),因此“await”將一個(gè)小書簽放在當(dāng)前位置,然后UI線程返回到它的正常處理(所有的視覺(jué)更新等)。
隨后,一旦硬盤驅(qū)動(dòng)器讀取了所有數(shù)據(jù),ReadAsync方法將其全部復(fù)制到allData字節(jié)數(shù)組中,任務(wù)現(xiàn)在就完成了,因此系統(tǒng)按門鈴,讓原始線程知道結(jié)果已經(jīng)準(zhǔn)備好了。原始線程說(shuō):“太棒了!讓我回到離開的地方!”一有機(jī)會(huì),它就會(huì)回到“await fs.ReadSync”,然后繼續(xù)下一步,返回allData數(shù)組,這個(gè)數(shù)組現(xiàn)在已經(jīng)填充了我們的數(shù)據(jù)。
如果你在一個(gè)接一個(gè)地看一個(gè)例子,并且使用的是最近的Visual Studio版本,你會(huì)注意到這一行:
ReadAHugeFile(hugeFile);
…現(xiàn)在,它用綠色下劃線表示,如果將鼠標(biāo)懸停在它上面,它會(huì)說(shuō),“因?yàn)檫@個(gè)調(diào)用沒(méi)有被等待,所以在調(diào)用完成之前,當(dāng)前方法的執(zhí)行將繼續(xù)?!笨紤]對(duì)調(diào)用的結(jié)果應(yīng)用'await'操作符。"
這是Visual Studio讓你知道它承認(rèn)ReadAHugeFile()是一個(gè)異步的方法,而不是返回一個(gè)結(jié)果,這也是返回任務(wù),所以如果你想等待結(jié)果,然后你就可以添加一個(gè)“await”:
await ReadAHugeFile(hugeFile);
…但如果我們這樣做了,那么你還必須更新方法簽名:
public async void Loopy()
注意,如果我們?cè)谝粋€(gè)不返回任何東西的方法上(void返回類型),那么我們不需要將返回類型包裝在Task<…>中。
但是,我們不要這樣做。相反,讓我們來(lái)了解一下我們可以用異步做些什么。
如果你不想等待ReadAHugeFile(hugeFile)的結(jié)果,因?yàn)槟憧赡懿魂P(guān)心最終的結(jié)果,但你不喜歡綠色下劃線/警告,你可以使用一個(gè)特殊的技巧來(lái)告訴.net。只需將結(jié)果賦給_字符,就像這樣:
_ = ReadAHugeFile(hugeFile);
這就是.net的語(yǔ)法,表示“我不在乎結(jié)果,但我不希望用它的警告來(lái)打擾我。”
好吧,我們?cè)囋噭e的。如果我們?cè)谶@一行上使用了await,那么它將等待第一個(gè)文件被異步讀取,然后等待第二個(gè)文件被異步讀取,最后等待第三個(gè)文件被異步讀取。但是…如果我們想要同時(shí)異步地讀取所有3個(gè)文件,然后在所有3個(gè)文件都完成之后,我們?cè)试S代碼繼續(xù)到下一行,該怎么辦?
有一個(gè)叫做Task.WhenAll()的方法,它本身是一個(gè)你可以await的異步方法。傳入其他任務(wù)對(duì)象的列表,然后等待它,一旦所有任務(wù)都完成,它就會(huì)完成。所以最簡(jiǎn)單的方法就是創(chuàng)建一個(gè)List
ListreadingTasks = new List ();
…然后,當(dāng)我們將每個(gè)ReadAHugeFile()調(diào)用中的Task添加到列表中時(shí):
foreach (var hugeFile in hugeFiles) { readingTasks.Add(ReadAHugeFile(hugeFile)); }
…最后我們 await Task.WhenAll():
await Task.WhenAll(readingTasks);
最終的方法是這樣的:
public async void Loopy() { var hugeFiles = new string[] { "Gr8Gonzos_Home_Movie_In_8k_Res.mkv", // 1 GB "War_And_Peace_In_150_Languages.rtf", // 1.2 GB "Cats_On_Catnip.mpg" // 0.9 GB }; ListreadingTasks = new List (); foreach (var hugeFile in hugeFiles) { readingTasks.Add(ReadAHugeFile(hugeFile)); } await Task.WhenAll(readingTasks); MessageBox.Show(sb.ToString()); }
當(dāng)涉及到并行活動(dòng)時(shí),一些I/O機(jī)制比其他機(jī)制工作得更好(例如,網(wǎng)絡(luò)請(qǐng)求通常比硬盤讀取工作得更好,但這取決于硬件),但原理是相同的。
現(xiàn)在,“await”操作符還要做的最后一件事是提取最終結(jié)果。所以在上面的例子中,ReadAHugeFile返回一個(gè)任務(wù)
byte[] data = await ReadAHugeFile(hugeFile);
再次強(qiáng)調(diào),await是一個(gè)神奇的小命令,它使異步編程變得非常簡(jiǎn)單,并為你處理各種各樣的小事情。
現(xiàn)在讓我們轉(zhuǎn)向多線程。
微軟有時(shí)會(huì)給你10種不同的方法來(lái)做同樣的事情,這就是它如何使用多線程。你有BackgroundWorker類、Thread和Task(它們有幾個(gè)變體)。最終,它們都做著相同的事情,只是有不同的功能?,F(xiàn)在,大多數(shù)人都使用Task,因?yàn)樗鼈兊脑O(shè)置和使用都很簡(jiǎn)單,而且如果你想這樣做的話(我們稍后會(huì)講到),它們也可以很好地與異步代碼交互。如果你好奇的話,關(guān)于這些具體區(qū)別有很多文章,但是我們?cè)谶@里使用任務(wù)。
要讓任何方法在單獨(dú)的線程中運(yùn)行,只需使用Task.Run()方法來(lái)執(zhí)行它。例如,假設(shè)你有這樣一個(gè)方法:
public void DoRandomCalculations(int howMany) { var rng = new Random(); for (int i = 0; i < howMany; i++) { int a = rng.Next(1, 1000); int b = rng.Next(1, 1000); int sum = 0; sum = a + b; } }
我們可以像這樣在當(dāng)前線程中調(diào)用它:
DoRandomCalculations(1000000);
或者我們可以讓另一個(gè)線程來(lái)做這個(gè)工作:
Task.Run(() => DoRandomCalculations(1000000));
當(dāng)然,有一些不同的版本,但這是總體思路。
Task. run()的一個(gè)優(yōu)點(diǎn)是它返回一個(gè)我們可以等待的任務(wù)對(duì)象。因此,如果想在一個(gè)單獨(dú)的線程中運(yùn)行一堆代碼,然后在進(jìn)入下一步之前等待它完成,你可以使用await,就像你在前面一節(jié)看到的那樣:
var finalData = await Task.Run(() => {});
關(guān)于C#中異步與多線程的作用有哪些問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識(shí)。