C#:異步編程和線程的使用(.NET4.5)

C#:異步編程和線程的使用(.NET 4.5 )

成都創(chuàng)新互聯(lián)是一家專(zhuān)注于成都網(wǎng)站建設(shè)、網(wǎng)站建設(shè)與策劃設(shè)計(jì),虹口網(wǎng)站建設(shè)哪家好?成都創(chuàng)新互聯(lián)做網(wǎng)站,專(zhuān)注于網(wǎng)站建設(shè)十余年,網(wǎng)設(shè)計(jì)領(lǐng)域的專(zhuān)業(yè)建站公司;建站業(yè)務(wù)涵蓋:虹口等地區(qū)。虹口做網(wǎng)站價(jià)格咨詢:18980820575

異步編程和線程處理是并發(fā)或并行編程非常重要的功能特征。為了實(shí)現(xiàn)異步編程,可使用線程也可以不用。將異步與線程同時(shí)講,將有助于我們更好的理解它們的特征。

本文中涉及關(guān)鍵知識(shí)點(diǎn)

1. 異步編程

2. 線程的使用

3. 基于任務(wù)的異步模式

4. 并行編程

5. 總結(jié)

  • 異步編程

什么是異步操作?異步操作是指某些操作能夠獨(dú)立運(yùn)行,不依賴(lài)主流程或主其他處理流程。通常情況下,C#程序從Main方法開(kāi)始,當(dāng)Main方法返回時(shí)結(jié)束。所有的操作都是按順序執(zhí)行的。執(zhí)行操作是有序列的,一個(gè)操作必須等到其前面的操作完成才能夠執(zhí)行。如以下代碼示例:

   1:  static void Main(string[] args)
   2:
   3:  {
   4:
   5:  DoTaskOne();
   6:
   7:  DoTaskTwo();
   8:
   9:  }

“DoTaskOne”方法結(jié)束后,DoTaskTwo()才能夠執(zhí)行。

異步編程中常用后臺(tái)運(yùn)行的方法體現(xiàn),主調(diào)用線程不會(huì)被阻塞。調(diào)用后臺(tái)運(yùn)行的方法后,執(zhí)行流程會(huì)立即返回到調(diào)用的線程并繼續(xù)執(zhí)行其他任務(wù)。后臺(tái)運(yùn)行方法通常是用線程或任務(wù)來(lái)實(shí)現(xiàn)。

在上面的例子中,在“DoTaskOne”方法調(diào)用成功后,如果“DoTaskOne”是異步調(diào)用,,執(zhí)行流程立即返回到Main方法中,并繼續(xù)執(zhí)行“DoTaskTwo” 方法。

C#提供了Thread類(lèi)創(chuàng)建線程實(shí)現(xiàn)異步編程,或者使用.NET提供的異步模式實(shí)現(xiàn)異步編程。.NET中提供了三種不同的異步模式:

1. 異步編程模型(APM)模式

2. 基于事件的異步模式(EAP)

3. 基于任務(wù)的異步模式(TAP)

前兩種模型微軟官方并不推薦使用,本文不再詳細(xì)描述。我們將詳細(xì)討論基于任務(wù)的異步模式(TAP):

  • 線程的使用

在.NET 4.5中引入了異步編程模式,大部分情況下都不需要我們手動(dòng)創(chuàng)建線程。編譯器已經(jīng)替代了開(kāi)發(fā)人員來(lái)完成這項(xiàng)工作。

創(chuàng)建新線程是非常耗時(shí)的。一般情況下,異步和并行編程使用 “基于任務(wù)的異步模式(TAP)”和“任務(wù)并行庫(kù)(TPL)”就夠了。如果需要控制線程的功能則需要使用其他模式。

TAP和TPL都是基于任務(wù)。一般來(lái)說(shuō)任務(wù)是從線程池中調(diào)用線程( 線程池.NET框架創(chuàng)建的和維護(hù)的線程集。如果我們使用任務(wù),就不需要直接調(diào)用線程池。

任務(wù)可以在以下情況運(yùn)行:

1. 在正在運(yùn)行的線程中

2. 在新線程中

3. 從線程池中的某一線程中

4. 沒(méi)有線程也可以運(yùn)行

如果使用任務(wù)機(jī)制,開(kāi)發(fā)人員就不必?fù)?dān)心線程的創(chuàng)建或使用,.NET框架已經(jīng)為我們解決了這一難題。

有時(shí)候需要控制線程,執(zhí)行以下操作:

1. 設(shè)置線程名稱(chēng)

2. 設(shè)置線程優(yōu)先級(jí)

3. 設(shè)置線程是前端或后端運(yùn)行

我們可以使用線程類(lèi)來(lái)創(chuàng)建線程。

使用Thread類(lèi)創(chuàng)建線程

Thread類(lèi)的構(gòu)造函數(shù)接收委托類(lèi)型的參數(shù)

1. ThreadStart:定義了返回值為空的方法,且不帶參數(shù)的方法。

2. ParameterizedThreadStart:定義了返回值為空且有一個(gè)object類(lèi)型的參數(shù)。

下面是一個(gè)簡(jiǎn)單的例子,使用 Start方法啟動(dòng)一個(gè)新線程:

   1:  static void Main(string[] args)
   2:
   3:  {
   4:
   5:  Thread thread = new Thread(DoTask);
   6:
   7:  thread.Start();// Start DoTask method in a new thread
   8:
   9:  //Do other tasks in main thread
  10:
  11:  }
  12:
  13:  static public void DoTask() {
  14:
  15:  //do something in a new thread
  16:
  17:  }

可以用Lamda表達(dá)式代替線程名稱(chēng):

   1:  static void Main(string[] args)
   2:
   3:  {
   4:
   5:  Thread thread = new Thread(() => {
   6:
   7:  //do something in a new thread
   8:
   9:  });
  10:
  11:  thread.Start();// Start a new thread
  12:
  13:  //Do other tasks in main thread
  14:
  15:  }

如果不需要引用變量,可如下直接啟動(dòng)線程:

   1:  static void Main(string[] args)
   2:
   3:  {
   4:
   5:  new Thread(() => {
   6:
   7:  //do something in a new thread
   8:
   9:  }).Start();// Start a new thread
  10:
  11:  //Do other tasks in main thread
  12:
  13:  }

但是,如果想控制線程對(duì)象,對(duì)線程設(shè)置一些屬性,需要在線程創(chuàng)建后引用線程變量。如下可給線程對(duì)象的不同屬性設(shè)值:

   1:  static void Main(string[] args)
   2:
   3:  {
   4:
   5:  Thread thread = new Thread(DoTask);
   6:
   7:  thread.Name = "My new thread";// Asigning name to the thread
   8:
   9:  thread.IsBackground = false;// Made the thread forground
  10:
  11:  thread.Priority = ThreadPriority.AboveNormal;// Setting thread priority
  12:
  13:  thread.Start();// Start DoTask method in a new thread
  14:
  15:  //Do other task in main thread
  16:
  17:  }

調(diào)用引用變量,可以執(zhí)行一些操作如中止線程或通過(guò)調(diào)用join方法等待阻塞線程。

如果需要通過(guò)函數(shù)傳值,可以給Start方法傳值。由于該方法的參數(shù)為Object類(lèi)型,因此需要強(qiáng)制轉(zhuǎn)換類(lèi)型。

   1:  static void Main(string[] args)
   2:
   3:  {
   4:
   5:  Thread thread = new Thread(DoTaskWithParm);
   6:
   7:  thread.Start("Passing string");// Start DoTaskWithParm method in a new thread
   8:
   9:  //Do other task in main thread
  10:
  11:  }
  12:
  13:  static public void DoTaskWithParm(object data)
  14:
  15:  {
  16:
  17:  //we need to cast the data to appropriate object
  18:
  19:  }

“async”和“await”關(guān)鍵字

.NET框架引入了兩個(gè)新的關(guān)鍵字來(lái)實(shí)現(xiàn)異步編程:“async”和“await”。使用 “await”的異步方法必須由“async”修飾符來(lái)聲明方法?!癮wait”關(guān)鍵字修飾調(diào)用異步方法。await 運(yùn)算符應(yīng)用于一個(gè)異步方法中的任務(wù)以掛起該方法的執(zhí)行,直到等待任務(wù)完成.如下: 

   1:  private async static void CallerWithAsync()// async modifier is used
   2:
   3:  {
   4:
   5:  string result = await GetSomethingAsync();// await is used before a method call. It suspends
   6:     //execution of CallerWithAsync() method and control returs to the calling thread that can
          //perform other task.
   7:
   8:  Console.WriteLine(result);
   9:     // this line would not be executed before GetSomethingAsync() //method completes
  10:
  11:  }

而“ async ”修飾符只能用于返回值為T(mén)ask類(lèi)型或Void的方法。它不能用于主程序的切入點(diǎn)。

所有的方法之前不能使用await關(guān)鍵字,使用“await”關(guān)鍵字方法必須返回 “可等待”類(lèi)型。以下屬于“可等待”類(lèi)型:

1. Task

2. Task<T>

3. 自定義“可等待”類(lèi)型。

  • 基于任務(wù)的異步模式

首先我們需要聲明一個(gè)返回類(lèi)型為T(mén)ask或Task<T>的異步方法。可以通過(guò)以下幾種方式創(chuàng)建任務(wù):

1. Task.Factory.StartNew方法:在之前的.NET版本(在.NET 4中),是創(chuàng)建和啟動(dòng)任務(wù)的主要方法。

2. Task.Run或Task.Run <T>方法:從.NET 4.5這個(gè)方法已經(jīng)被使用。此方法足以滿足常見(jiàn)情況。

3. Task.FromResult方法:如果結(jié)果是已計(jì)算,就可以用這個(gè)方法來(lái)創(chuàng)建任務(wù)。

創(chuàng)建并等待一個(gè)任務(wù)

使用Task.Run <T>方法創(chuàng)建Task。該方法將特定工作按順序排列在線程池中運(yùn)行,并返回工作的任務(wù)句柄。需要以下步驟從同步方法中創(chuàng)建異步任務(wù):

1. 假設(shè)下面方法是同步的,但需要一定的時(shí)間來(lái)完成:

   1:  static string Greeting(string name)
   2:
   3:  {
   4:
   5:  Thread.Sleep(3000);
   6:
   7:  return string.Format("Hello, {0}", name);
   8:
   9:  }

2. 要以異步方式訪問(wèn)此方法,必須以異步方式封裝。命名為“GreetingAsync”。增加“Async”的后綴命名異步方法。

 

   1:  static Task<string> GreetingAsync(string name)
   2:
   3:  {
   4:
   5:  return Task.Run<string>(() =>
   6:
   7:  {
   8:
   9:  return Greeting(name);
  10:
  11:  });
  12:
  13:  }

3.現(xiàn)在,可通過(guò)使用的await關(guān)鍵字調(diào)用異步方法GreetingAsync

 

   1:  private async static void CallWithAsync()
   2:
   3:  {
   4:
   5:  //some other tasks
   6:
   7:  string result = await GreetingAsync("Bulbul");
   8:
   9:  //We can add multiple “await” in same “async” method
  10:
  11:  //string result1 = await GreetingAsync(“Ahmed”);
  12:
  13:  //string result2 = await GreetingAsync(“Every Body”);
  14:
  15:  Console.WriteLine(result);
  16:
  17:  }

當(dāng)“CallWithAsync”方法被調(diào)用時(shí),與常規(guī)的同步方法一樣執(zhí)行,直到遇到“await”的關(guān)鍵字。當(dāng)它執(zhí)行到 await的關(guān)鍵字會(huì)處理執(zhí)行,并開(kāi)始等待“GreetingAsync(” Bulbul “)” 方法被完成。同時(shí),程序流將返回” CallWithAsync “方法的調(diào)用者,并繼續(xù)執(zhí)行調(diào)用者的任務(wù)。

當(dāng)“GreetingAsync(" Bulbul ") 方法完成,“CallWithAsync”的方法恢復(fù) “await關(guān)鍵字后的其他任務(wù)。在本實(shí)例中,將繼續(xù)執(zhí)行的代碼“Console.WriteLine(result)”

4. 使用任務(wù)持續(xù):Task類(lèi) “ContinueWith”的方法定義了Task完成后被調(diào)用的代碼。

   1:  private static void CallWithContinuationTask()
   2:
   3:  {
   4:
   5:  Task<string> t1 = GreetingAsync("Bulbul");
   6:
   7:  t1.ContinueWith(t =>
   8:
   9:  {
  10:
  11:  string result = t.Result;
  12:
  13:  Console.WriteLine(result);
  14:
  15:  });
  16:
  17:  }

如果使用“ContinueWith”的方法就不需要使用“await“關(guān)鍵字,編譯器會(huì)自動(dòng)在合適的位置中添加“await”關(guān)鍵字。

等候多個(gè)異步方法。

看看下面的代碼:

   1:  private async static void CallWithAsync()
   2:
   3:  {
   4:
   5:  string result = await GreetingAsync("Bulbul");
   6:
   7:  string result1 = await GreetingAsync(&ldquo;Ahmed&rdquo;);
   8:
   9:  Console.WriteLine(result);
  10:
  11:  Console.WriteLine(result1);
  12:
  13:  }

有兩個(gè)正在等待調(diào)用函數(shù)序列。“GreetingAsync(” Ahmed “)” 會(huì)在完成第一個(gè)呼叫“GreetingAsync(” Bulbul “)” 之后啟動(dòng)。如果“result”和上面的代碼“result1”是獨(dú)立的,那么連續(xù)的“awiating”并不是一個(gè)好的做法。

在這種情況下,我們可以簡(jiǎn)化調(diào)用方法,不需要添加多個(gè)“await”關(guān)鍵字,只在一個(gè)地方添加await關(guān)鍵字,如下所示,這種情況下,該方法的調(diào)用都可以并行執(zhí)行。

   1:  private async static void MultipleAsyncMethodsWithCombinators()
   2:
   3:  {
   4:
   5:  Task<string> t1 = GreetingAsync("Bulbul");
   6:
   7:  Task<string> t2 = GreetingAsync("Ahmed");
   8:
   9:  await Task.WhenAll(t1, t2);
  10:
  11:  Console.WriteLine("Finished both methods.\n " +
  12:
  13:  "Result 1: {0}\n Result 2: {1}", t1.Result, t2.Result);
  14:
  15:  }

在這里,我們使用Task.WhenAll連接器。Task.WhenAll創(chuàng)建一個(gè)任務(wù),將完成所有的提供的任務(wù)。Task類(lèi)也有其他的結(jié)合器。Task.WhenAny,當(dāng)所任務(wù)鏈中所有的任務(wù)完成時(shí),結(jié)束使用。

處理異常

必須把“await的代碼塊放在try塊內(nèi)捕獲異常。

   1:  private async static void CallWithAsync()
   2:
   3:  {
   4:
   5:  try
   6:
   7:  {
   8:
   9:  string result = await GreetingAsync("Bulbul");
  10:
  11:  }
  12:
  13:  catch (Exception ex)
  14:
  15:  {
  16:
  17:  Console.WriteLine(&ldquo;handled {0}&rdquo;, ex.Message);
  18:
  19:  }
  20:
  21:  }

如果try塊中有多個(gè)“await”,只有第一個(gè)” await“異常會(huì)被處理,其他“await”將無(wú)法被捕捉。如果希望所有的方法都能捕獲異常,不能使用“await”關(guān)鍵字調(diào)用方法,使用Task.WhenAll來(lái)執(zhí)行任務(wù)。

   1:  private async static void CallWithAsync()
   2:
   3:  {
   4:
   5:  try
   6:
   7:  {
   8:
   9:  Task<string> t1 = GreetingAsync("Bulbul");
  10:
  11:  Task<string> t2 = GreetingAsync("Ahmed");
  12:
  13:  await Task.WhenAll(t1, t2);
  14:
  15:  }
  16:
  17:  catch (Exception ex)
  18:
  19:  {
  20:
  21:  Console.WriteLine(&ldquo;handled {0}&rdquo;, ex.Message);
  22:
  23:  }
  24:
  25:  }

捕獲所有任務(wù)的錯(cuò)誤一種方法是在try塊之外聲明任務(wù),這樣可以從try塊進(jìn)行訪問(wèn),并檢查任務(wù)的“IsFaulted”屬性。如果它存在異常那么“IsFaulted”屬性值為T(mén)rue,就可捕獲任務(wù)實(shí)例的內(nèi)部異常。

還有另一個(gè)更好的辦法:

   1:  static async void ShowAggregatedException()
   2:
   3:  {
   4:
   5:  Task taskResult = null;
   6:
   7:  try
   8:
   9:  {
  10:  Task<string> t1 = GreetingAsync("Bulbul");
  11:
  12:  Task<string> t2 = GreetingAsync("Ahmed");
  13:
  14:  await (taskResult = Task.WhenAll(t1, t2));
  15:
  16:  }
  17:
  18:  catch (Exception ex)
  19:
  20:  {
  21:
  22:  Console.WriteLine("handled {0}", ex.Message);
  23:
  24:  foreach (var innerEx in taskResult.Exception.InnerExceptions)
  25:
  26:  {
  27:  Console.WriteLine("inner exception {0}", nnerEx.Message); }
  28:  }
  29:
  30:  }

取消任務(wù)

在此之前,如果從線程池中調(diào)用線程,線程是不可能取消?,F(xiàn)在,Task類(lèi)提供了一個(gè)方法基于CancellationTokenSource類(lèi)能夠取消已啟動(dòng)的任務(wù),取消任務(wù)步驟:

1. 異步方法應(yīng)該除外 “ CancellationToken” 參數(shù)類(lèi)型

2. 創(chuàng)建CancellationTokenSource類(lèi)實(shí)例:

    var cts =new CancellationTokenSource();

3. 傳遞CancellationToken,如:

   1:    Task<string> t1 = GreetingAsync("Bulbul", cts.Token);

4. 長(zhǎng)時(shí)間運(yùn)行的方法中,必須調(diào)用CancellationToken 的ThrowIfCancellationRequested()方法。

 

   1:   static string Greeting(string name, CancellationToken token){
   2:
   3:  Thread.Sleep(3000);
   4:
   5:  token. ThrowIfCancellationRequested();
   6:
   7:  return string.Format("Hello, {0}", name);
   8:
}

5. 從等待的Task中捕獲 OperationCanceledException異常。

 6. 如果通過(guò)調(diào)用CancellationTokenSource的實(shí)例的方法執(zhí)行取消操作,將從長(zhǎng)時(shí)間運(yùn)行操作中拋出OperationCanceledException異常。也可以設(shè)置取消的時(shí)間。以下是完整的代碼,一秒后執(zhí)行取消操作:

   1:   static void Main(string[] args)
   2:
   3:   {
   4:   CallWithAsync();
   5:
   6:  Console.ReadKey();
   7:
   8:   }
   9:
  10:
  11:  async static void CallWithAsync()
  12:
  13:  {
  14:
  15:  try
  16:
  17:   {
  18:
  19:  CancellationTokenSource source = new CancellationTokenSource();
  20:
  21:  source.CancelAfter(TimeSpan.FromSeconds(1));
  22:
  23:   var t1 = await GreetingAsync("Bulbul", source.Token);
  24:   }
  25:
  26:   catch (OperationCanceledException ex)
  27:
  28:  {
  29:
  30:   Console.WriteLine(ex.Message);
  31:
  32:   }
  33:
  34:   }
  35:
  36:  static Task<string> GreetingAsync(string name, CancellationToken token)
  37:
  38:   {
  39:
  40:   return Task.Run<string>(() =>
  41:
  42:   {
  43:
  44:   return Greeting(name, token);
  45:
  46:   });
  47:  }
  48:
  49:
  50:   static string Greeting(string name, CancellationToken token)
  51:
  52:   {
  53:
  54:  Thread.Sleep(3000);
  55:   token.ThrowIfCancellationRequested();
  56:
  57:   return string.Format("Hello, {0}", name);
  58:
  59:   }
  60:
  • 并行編程

.NET 4.5及以上版本推出“Parallel類(lèi),是線程類(lèi)的抽象。使用“Parallel”類(lèi),我們可以實(shí)現(xiàn)并行。并行與線程不同,它使用所有可用的CPU或內(nèi)核的。以下兩種類(lèi)型的并行是可行:

  1. 數(shù)據(jù)并行:如果我們有數(shù)據(jù)的大集合,我們希望在每個(gè)數(shù)據(jù)的某些操作進(jìn)行并行使用,那么就可以使用數(shù)據(jù)并行。Parallel類(lèi)有靜態(tài)For或ForEach來(lái)執(zhí)行數(shù)據(jù)并行行,如

   1:  ParallelLoopResult result =
   2:                      Parallel.For(0, 100, async (int i) =>
   3:                      {
   4:                          Console.WriteLine("{0}, task: {1}, thread: {2}", i,
   5:                          Task.CurrentId, Thread.CurrentThread.ManagedThreadId);
   6:                          await Task.Delay(10);
   7:
   8:                });
      

For或ForEach方法可以在多線程中和且索引無(wú)序可以是無(wú)序的。

如果想停止并行For或ForEach方法,可通過(guò)ParallelLoopState作為參數(shù),并根據(jù)需要打破循環(huán)的狀態(tài),跳出循環(huán)。

   1:  ParallelLoopResult result =
   2:                      Parallel.For(0, 100, async (int i, ParallelLoopState pls) =>
   3:                      {
   4:                          Console.WriteLine("{0}, task: {1}, thread: {2}", i,
   5:                          Task.CurrentId, Thread.CurrentThread.ManagedThreadId);
   6:                          await Task.Delay(10);
   7:                          if (i > 5) pls.Break();
   8:                });

     2. 任務(wù)并行:如果想要同時(shí)運(yùn)行多個(gè)任務(wù)的,我們可以通過(guò)調(diào)用Parallel類(lèi)的invoke方法使用任務(wù)并行Parallel.Invoke方法接收委托行為的數(shù)組。例如:

   1:  static void ParallelInvoke()
   2:
   3:  {
   4:
   5:  Parallel.Invoke(MethodOne, MethodTwo);
   6:
   7:  }
   8:
  • 結(jié)論

本文詳細(xì)介紹了.NET Framework 4.5提供的異步編程技術(shù)及細(xì)節(jié)。

原文鏈接:http://www.codeproject.com/Articles/996857/Asynchronous-programming-and-Threading-in-Csharp-N

文章名稱(chēng):C#:異步編程和線程的使用(.NET4.5)
轉(zhuǎn)載來(lái)源:http://muchs.cn/article42/gedchc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導(dǎo)航、標(biāo)簽優(yōu)化、定制網(wǎng)站、品牌網(wǎng)站制作手機(jī)網(wǎng)站建設(shè)、關(guān)鍵詞優(yōu)化

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

商城網(wǎng)站建設(shè)