>>回傳《C# 并發編程》
- 1. 概念介紹
- 2. 異步編程
- 2.1. async運行程序
- 2.2. async運行中同步背景關系簡介
- 2.3. 創建Task實體
- 2.4. 捕獲異步例外型別
- 3. 并行編程
- 3.1. Parallel
- 3.2. 例外處理
- 3.3. 注意事項
- 4. 回應式編程
1. 概念介紹
現在我們先說明幾個概念:
- 并發
- 就是同時做多件事情,比如:
- 程式寫入資料庫的同時回應用戶輸入
- 服務器處理第一個請求的同時回應第二個請求,
- 就是同時做多件事情,比如:
- 多執行緒
- 是并發的一種形式,它采用多個執行緒來執行程式,
- 注意: 多執行緒是并發的一種形式,但并不是唯一的形式,
- 多執行緒是比較基礎的技術,我們需要理解,知曉原理,但是真正使用時最好使用對多執行緒進行封裝的類,這樣能更好的節省資源,減少問題的產生,
- 是并發的一種形式,它采用多個執行緒來執行程式,
- 并行處理
- 把正在執行的大量的任務分割成小塊,分配給多個同時運行的執行緒,
- 這樣會使處理器的利用效率最大化,使用時需要注意因為控制不好的化會在短時間內極大的降低服務器的處理性能
- 異步編程
- 并發的一種形式,它采用未來模式(future)或回呼機制(callback),以避免產生不必要的執行緒,
- 在 .NET 中,新版中使用
Task和Task<TResult>型別實作未來模式,在老式異步編程 API 中,采用回呼或事件(event)
- 在 .NET 中,新版中使用
- 也是關鍵字
async和await解決的問題
- 并發的一種形式,它采用未來模式(future)或回呼機制(callback),以避免產生不必要的執行緒,
- 回應式編程
- 一種宣告式的編程模式,程式在該模式中對事件做出回應,
2. 異步編程
2.1. async運行程序
async方法在開始時以同步方式執行,在async方法內部,運行到await關鍵字會執行一個異步等待- 它首先檢查操作是否已經完成,如果完成了,就繼續運行 (同步方式),
- 否則,它會暫停
async方法,并回傳,留下一個未完成的task, - 一段時間后,
await住的操作完成,async方法就恢復運行(不一定是原來的執行緒,具體看同步背景關系的配置),
2.2. async運行中同步背景關系簡介
- 最常見的情況是,用
await陳述句等待一個任務完成,這時會捕捉同步背景關系,- 如果當前
SynchronizationContext不為空,這個背景關系就是當前SynchronizationContext, - 如果當前
SynchronizationContext為空,則這個背景關系為當前 TaskScheduler,
- 如果當前
- 該方法會在這個背景關系中繼續運行,
- 運行 UI 執行緒時采用 UI 背景關系
- 處理 ASP.NET 請求時采用 ASP.NET 請求背景關系
- 其他很多情況下則采用執行緒池背景關系(背景關系為 null 時)
注意: 最好的做法是,在核心庫代碼中一直使用
ConfigureAwait,在外圍的用戶界面代碼中,只在需要時才恢復背景關系,
2.3. 創建Task實體
- 有兩種基本的方法可以創建 Task 實體,
- 有些任務表示 CPU 需要實際執行的指令,創建這種計算類的任務時,使用
Task.Run- 如UI執行緒觸發的事件,如讀取目錄資訊,配合
await關鍵字,將任務交給執行緒池完成,解決讀取時表單卡頓情況
- 如UI執行緒觸發的事件,如讀取目錄資訊,配合
- 如需要按照特定的計劃運行,則用
TaskFactory.StartNew - 其他的任務表示一個通知( notification 操作會在回呼中完成再通知回來),創建這種基于事件的任務時,使用
TaskCompletionSource<T>,- 大部分 I/O 型任務采用
TaskCompletionSource<T>,
- 大部分 I/O 型任務采用
- 有些任務表示 CPU 需要實際執行的指令,創建這種計算類的任務時,使用
2.4. 捕獲異步例外型別
- 捕獲
await拋出的例外,我們更想要
try
{
await Task.Run(() => throw new NotSupportedException());
}
catch (Exception ex)
{
//print: NotSupportedException
Console.WriteLine(ex.GetType().Name);
}
Wait()方法,例外型別被包裝
try
{
Task task = Task.Run(() => throw new NotSupportedException());
task.Wait();
}
catch (Exception ex)
{
//print: AggregateException
Console.WriteLine(ex.GetType().Name);
}
3. 并行編程
- 并行編程可臨時提高 CPU 利用率,以提高吞吐量,
- 若客戶端系統中的 CPU 經常處于空閑狀態,這個方法就非常有用
- 通常并不適合服務器系統,將降低本身的并行處理能力,并且不會有實際的好處,
反面教材: 之前在作業中出現一起事故,實施好的專案,3個月后每天凌晨出現大量設備掉線的情況,
- 由于資料超時時間時3個月,而且發現出現問題的日志和資料清理發生時間有關聯關系
- 排查代碼發現檔案清理器,清理資料使用的Parallel類,并行洗掉檔案,而且沒有對并發數限制
- 檔案清理器運行時,導致服務器性能急劇下降,造成處理設備訊息延遲,心跳超時導致掉線
- 重構了檔案清理器代碼,解決了這個問題
- 資料并行(data parallelism):
- 是指有大量的資料需要處理,并且每一塊資料的處理程序基本上是彼此獨立的,
- 任務并行(task parallelim):
- 是指需要執行大量任務,并且每個任務的執行程序基本上是彼此獨立的,
3.1. Parallel
不保證順序執行,
//ForEach
int[] arr = new int[] { 1, 2, 3, 4 };
Parallel.ForEach(arr, item => Console.Write(item));
System.Console.WriteLine();
//PLINQ
var sum = arr.AsParallel().Select(item => item * 2).Sum();
System.Console.WriteLine($"sum:{sum}.");
//Invoke
int num = 10;
Parallel.Invoke(
() => num += 2,
() => num -= 2,
() => num -= num,
() => num += 2
);
System.Console.WriteLine($"num:{num}.");
/*
print:
1243
sum:20.
num:0.
*/
3.2. 例外處理
系統會把這些例外封裝在 AggregateException 類里,在程式中拋給代碼, 這一特點對所有方法都是一樣的,包括 Parallel.ForEach、Paralle.lInvoke、Task.Wait 等, AggregateException 型別有幾個實用的 Flatten 和 Handle 方法,用來簡化錯誤處理的代碼:
try
{
Parallel.Invoke(() => { throw new Exception(); },
() => { throw new Exception(); });
}
catch (AggregateException ex)
{
ex.Handle(exception =>
{
Console.WriteLine(exception);
return true; //“已經處理”
});
}
3.3. 注意事項
在撰寫任務并行程式時,要格外留意下閉包(closure)捕獲的變數, 記住閉包捕獲的是參考(不是值),因此可以在結束時以不明顯地方式地分享這些變數,
- 任務不要特別短
- 沒必要用,直接同步執行
- 也不要特別長
- 應采用更可控的方式,削峰設計
4. 回應式編程
如果事件中帶有引數,那么最好 采用回應式編程,而不是常規的事件處理程式,
//System.Runtime.dll namespace:System 中定義了這些介面
interface IObserver<in T>
{
void OnNext(T item);
void OnCompleted();
void one rror(Exception error);
}
interface IObservable<out T>
{
IDisposable Subscribe(IObserver<T> observer);
}
Rx(Rx-Main)中定義了回應式編程的封裝,后面會有介紹,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/77402.html
標籤:C#
上一篇:C#面向物件--結構
下一篇:C# 并發編程
