您好,登錄后才能下訂單哦!
這篇文章主要講解了“C#多線程舉例分析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“C#多線程舉例分析”吧!
線程(英語:thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發多個線程,每條線程并行執行不同的任務。進程是資源分配的基本單位。所有與該進程有關的資源,都被記錄在進程控制塊PCB中。以表示該進程擁有這些資源或正在使用它們。
業務場景:用戶點擊一個按鈕,然后做一個耗時的業務。同步方式代碼如下所示:
private void btnSync_Click(object sender, EventArgs e) { Stopwatch watch = Stopwatch.StartNew(); watch.Start(); Console.WriteLine("************btnSync_Click同步方法 開始,線程ID= {0}************", Thread.CurrentThread.ManagedThreadId); for (int i = 0; i < 5; i++) { string name = string.Format("{0}_{1}", "btnSync_Click", i); this.DoSomethingLong(name); } Console.WriteLine("************btnSync_Click同步方法 結束,線程ID= {0}************", Thread.CurrentThread.ManagedThreadId); watch.Stop(); Console.WriteLine("************總耗時= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); } /// <summary> /// 模擬做一些長時間的工作 /// </summary> /// <param name="name"></param> private void DoSomethingLong(string name) { Console.WriteLine("************DoSomethingLong 開始 name= {0} 線程ID= {1} 時間 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff")); //CPU計算累加和 long rest = 0; for (int i = 0; i < 1000000000; i++) { rest += i; } Console.WriteLine("************DoSomethingLong 結束 name= {0} 線程ID= {1} 時間 = {2} 結果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest); }
同步方式輸出結果,如下所示:
通過對以上示例進行分析,得出結論如下:
同步方式按順序依次執行。
同步方式業務和UI采用采用同一線程,都是主線程。
同步方式如果執行操作比較耗時,前端UI會卡住,無法響應用戶請求。
同步方式比較耗時【本示例9.32秒】
如何優化同步方式存在的問題呢?答案是由同步方式改為異步異步多線程方式。代碼如下所示:
private void btnAsync_Click(object sender, EventArgs e) { Stopwatch watch = Stopwatch.StartNew(); watch.Start(); Console.WriteLine("************btnAsync_Click異步方法 開始,線程ID= {0}************", Thread.CurrentThread.ManagedThreadId); Action<string> action = new Action<string>(DoSomethingLong); for (int i = 0; i < 5; i++) { string name = string.Format("{0}_{1}", "btnAsync_Click", i); action.BeginInvoke(name,null,null); } Console.WriteLine("************btnAsync_Click異步方法 結束,線程ID= {0}************", Thread.CurrentThread.ManagedThreadId); watch.Stop(); Console.WriteLine("************總耗時= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); }
異步方式出結果,如下所示:
通過對以上示例進行分析,得出結論如下:
異步方式不是順序執行,即具有無序性。
異步方式采用多線程方式,和UI不是同一個線程,所以前端UI不會卡住。
異步多線程方式執行時間短,響應速度快。
通過觀察任務管理器,發現同步方式比較耗時間,異步方式比較耗資源【本例是CPU密集型操作】,屬于以資源換性能。同步方式和異步方式的CPU利用率,如下圖所示:
通過上述例子,發現由于采用異步的原因,線程還未結束,但是排在后面的語句就先執行,所以統計的程序執行總耗時為0秒。為了優化此問題,采用async與await組合方式執行,代碼如下所示:
private async void btnAsync2_Click(object sender, EventArgs e) { Stopwatch watch = Stopwatch.StartNew(); watch.Start(); Console.WriteLine("************btnAsync_Click2異步方法 開始,線程ID= {0}************", Thread.CurrentThread.ManagedThreadId); await DoAsync(); Console.WriteLine("************btnAsync_Click2異步方法 結束,線程ID= {0}************", Thread.CurrentThread.ManagedThreadId); watch.Stop(); Console.WriteLine("************總耗時= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); } /// <summary> /// 異步方法 /// </summary> /// <returns></returns> private async Task DoAsync() { Action<string> action = new Action<string>(DoSomethingLong); List<IAsyncResult> results = new List<IAsyncResult>(); for (int i = 0; i < 5; i++) { string name = string.Format("{0}_{1}", "btnAsync_Click", i); IAsyncResult result = action.BeginInvoke(name, null, null); results.Add(result); } await Task.Run(()=> { while (true) { for (int i = 0; i < results.Count; i++) { var result = results[i]; if (result.IsCompleted) { results.Remove(result); break; } } if (results.Count < 1) { break; } Thread.Sleep(200); } }); }
經過優化,執行結果如下所示:
通過異步多線程優化后的執行結果,進行分析后得出的結論如下:
Action的BeginInvoke,會返回IAsyncResult接口,通過接口可以判斷是否完成。
如果有多個Action的多線程調用,可以通過List方式進行。
async與await組合,可以實現異步調用,防止線程阻塞。
通過以上方式,采用異步多線程的方式,共耗時3.26秒,比同步方式的9.32秒,提高了2.85倍,并非線性增加。且每次執行的總耗時會上下浮動,并非固定值。
上述async與await組合,是一種實現異步調用的方式,其實Action本身也具有回調函數【AsyncCallback】,通過回調函數一樣可以實現對應功能。具體如下所示:
/// <summary> /// 異步回調 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnAsync3_Click(object sender, EventArgs e) { Stopwatch watch = Stopwatch.StartNew(); watch.Start(); Console.WriteLine("************btnAsync_Click3異步方法 開始,線程ID= {0}************", Thread.CurrentThread.ManagedThreadId); Action action = DoAsync3; AsyncCallback asyncCallback = new AsyncCallback((ar) => { if (ar.IsCompleted) { Console.WriteLine("************btnAsync_Click3異步方法 結束,線程ID= {0}************", Thread.CurrentThread.ManagedThreadId); watch.Stop(); Console.WriteLine("************總耗時= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); } }); action.BeginInvoke(asyncCallback, null); } private void DoAsync3() { Action<string> action = new Action<string>(DoSomethingLong); List<IAsyncResult> results = new List<IAsyncResult>(); for (int i = 0; i < 5; i++) { string name = string.Format("{0}_{1}", "btnAsync_Click3", i); IAsyncResult result = action.BeginInvoke(name, null, null); results.Add(result); } while (true) { for (int i = 0; i < results.Count; i++) { var result = results[i]; if (result.IsCompleted) { results.Remove(result); break; } } if (results.Count < 1) { break; } Thread.Sleep(200); } }
異步回調執行示例,如下所示:
通過對異步回調方式執行結果進行分析,結論如下所示:
通過觀察線程ID可以發現,由于對循環計算的功能進行了封裝,為一個獨立的函數,所以在Action通過BeginInvoke發起時,又是一個新的線程。
通過async和await在通過Task.Run方式返回時,也會重新生成新的線程。
通過回調函數,可以保證異步線程的執行順序。
通過Thread.Sleep(200)的方式進行等待,會有一定時間范圍延遲。
信號量方式是通過BeginInvoke返回值IAsyncResult中的異步等待AsyncWaitHandle觸發信號WaitOne,可以實現信號的實時響應,具體代碼如下:
private void btnAsync4_Click(object sender, EventArgs e) { Stopwatch watch = Stopwatch.StartNew(); watch.Start(); Console.WriteLine("************btnAsync_Click4異步方法 開始,線程ID= {0}************", Thread.CurrentThread.ManagedThreadId); Action action = DoAsync3; var asyncResult = action.BeginInvoke(null, null); //此處中間可以做其他的工作,然后在最后等待線程的完成 asyncResult.AsyncWaitHandle.WaitOne(); Console.WriteLine("************btnAsync_Click4異步方法 結束,線程ID= {0}************", Thread.CurrentThread.ManagedThreadId); watch.Stop(); Console.WriteLine("************總耗時= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); }
信號量示例截圖如下所示:
通過對異步信號量方式的測試結果進行分析,得出結論如下:
信號量方式會造成線程的阻塞,且會造成前端界面卡死。
信號量方式適用于異步方法和等待完成之間還有其他工作需要處理的情況。
WaitOne可以設置超時時間【最多可等待時間】。
上述示例的委托都是無返回值類型的,那么對于有返回值的函數,如何獲取呢?答案就是采用Func。示例如下所示:
private void btnAsync5_Click(object sender, EventArgs e) { Stopwatch watch = Stopwatch.StartNew(); watch.Start(); Console.WriteLine("************btnAsync5_Click異步方法 開始,線程ID= {0}************", Thread.CurrentThread.ManagedThreadId); string name = string.Format("{0}_{1}", "btnAsync_Click5", 0); Func<string, int> func = new Func<string, int>(DoSomethingLongAndReturn); IAsyncResult asyncResult = func.BeginInvoke(name, null, null); //此處中間可以做其他的工作,然后在最后等待線程的完成 int result = func.EndInvoke(asyncResult); Console.WriteLine("************btnAsync5_Click異步方法 結束,線程ID= {0},返回值={1}************", Thread.CurrentThread.ManagedThreadId,result); watch.Stop(); Console.WriteLine("************總耗時= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); } private int DoSomethingLongAndReturn(string name) { Console.WriteLine("************DoSomethingLong 開始 name= {0} 線程ID= {1} 時間 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff")); //CPU計算累加和 long rest = 0; for (int i = 0; i < 1000000000; i++) { rest += i; } Console.WriteLine("************DoSomethingLong 結束 name= {0} 線程ID= {1} 時間 = {2} 結果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest); return DateTime.Now.Day; }
采用Func方式的EndInvoke,可以獲取返回值,示例如下:
通過對Func方式的EndInvoke方法的示例進行分析,得出結論如下所示:
在主線程中調用EndInvoke,會進行阻塞,前端頁面卡死。
Func的返回值是泛型類型,可以返回任意類型的值。
為了解決以上獲取返回值時,前端頁面卡死的問題,可以采用回調函數進行解決,如下所示:
private void btnAsync6_Click(object sender, EventArgs e) { Stopwatch watch = Stopwatch.StartNew(); watch.Start(); Console.WriteLine("************btnAsync6_Click異步方法 開始,線程ID= {0}************", Thread.CurrentThread.ManagedThreadId); string name = string.Format("{0}_{1}", "btnAsync_Click6", 0); Func<string, int> func = new Func<string, int>(DoSomethingLongAndReturn); AsyncCallback callback = new AsyncCallback((asyncResult) => { int result = func.EndInvoke(asyncResult); Console.WriteLine("************btnAsync6_Click異步方法 結束,線程ID= {0},返回值={1}************", Thread.CurrentThread.ManagedThreadId, result); watch.Stop(); Console.WriteLine("************總耗時= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00")); }); func.BeginInvoke(name, callback, null); }
采用回調方式,示例截圖如下:
通過對回調方式的示例進行分析,得出結論如下:
異步回調函數中調用EndInvoke,可以直接返回,不再阻塞。
異步回調方式,前端UI線程不再卡住。
感謝各位的閱讀,以上就是“C#多線程舉例分析”的內容了,經過本文的學習后,相信大家對C#多線程舉例分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。