併發編程相關概念

>>返回《C# 併發編程》
html

1. 概念介紹

如今咱們先說明幾個概念:數據庫

  • 併發
    • 就是同時作多件事情,好比:
      • 程序寫入數據庫的同時響應用戶輸入
      • 服務器處理第一個請求的同時響應第二個請求。
  • 多線程
    • 是併發的一種形式,它採用多個線程來執行程序,
      • 注意: 多線程是併發的一種形式,但並非惟一的形式。
    • 多線程是比較基礎的技術,咱們須要理解,知曉原理,可是真正使用時最好使用對多線程進行封裝的類,這樣能更好的節省資源,減小問題的產生。
  • 並行處理
    • 把正在執行的大量的任務分割成小塊,分配給多個同時運行的線程。
    • 這樣會使處理器的利用效率最大化,使用時須要注意由於控制很差的化會在短期內極大的下降服務器的處理性能
  • 異步編程
    • 併發的一種形式,它採用將來模式(future)或回調機制(callback),以免產生沒必要要的線程
      • 在 .NET 中,新版中使用 TaskTask<TResult>類型實現將來模式,在老式異步編程 API 中,採用回調或事件(event)
    • 也是關鍵字 asyncawait 解決的問題
  • 響應式編程
    • 一種聲明式的編程模式,程序在該模式中對事件作出響應。

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 關鍵字,將任務交給線程池完成,解決讀取時窗體卡頓狀況
    • 如須要按照特定的計劃運行,則用 TaskFactory.StartNew
    • 其餘的任務表示一個通知( notification 操做會在回調中完成再通知回來),建立這種基於事件的任務時,使用 TaskCompletionSource<T>
      • 大部分 I/O 型任務採用 TaskCompletionSource<T>

2.4. 捕獲異步異常類型

  1. 捕獲await拋出的異常,咱們更想要
try
{
    await Task.Run(() => throw new NotSupportedException());
}
catch (Exception ex)
{
    //print: NotSupportedException
    Console.WriteLine(ex.GetType().Name);
}
  1. 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個月後天天凌晨出現大量設備掉線的狀況。服務器

  1. 因爲數據超時時間時3個月,並且發現出現問題的日誌和數據清理髮生時間有關聯關係
  2. 排查代碼發現文件清理器,清理數據使用的Parallel類,並行刪除文件,並且沒有對併發數限制
  3. 文件清理器運行時,致使服務器性能急劇降低,形成處理設備消息延遲,心跳超時致使掉線
  4. 重構了文件清理器代碼,解決了這個問題
  • 數據並行(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 OnError(Exception error);
}
interface IObservable<out T>
{
    IDisposable Subscribe(IObserver<T> observer);
}

Rx(Rx-Main)中定義了響應式編程的封裝,後面會有介紹。async

相關文章
相關標籤/搜索