C#異步編程之基於任務的異步模式


http://www.cnblogs.com/afei-24/p/6757361.html該文講了基於任務的編程,這里再詳細介紹一下。
一.延續任務

  

private async static void CallerWithAsync()
    {
      string result = await GreetingAsync("Stephanie");
      Console.WriteLine(result);
    }

    static Task<string> GreetingAsync(string name)
    {
      return Task.Run<string>(() =>
        {
            Thread.Sleep(10000);
            return name;
        });
    }
    
    GreetingAsync方法返回一個Task<string>對象。該Task<string>對象包含任務創建的信息,並保存到任務完成。Task類的ContinueWith方法定義了任務完成后就調用的代碼。
        private static void CallerWithContinuationTask()
        {

          var t1 = GreetingAsync("Stephanie");


          t1.ContinueWith(t =>
            {
              string result = t.Result;
              Console.WriteLine(result);
            });


        }

  由於不使用await,線程不會在方法中等待,會執行完CallerWithContinuationTask()的代碼。不會再ContinueWith這里等待,所以需要一個前台線程,不然會關閉所以線程。

二.同步上下文
  CallerWithAsync和CallerWithContinuationTask方法在方法的不同階段使用了不同的線程。

  

static Task<string> GreetingAsync(string name)
        {
          return Task.Run<string>(() =>
            {
                Thread.Sleep(10000);
              Console.WriteLine("running greetingasync in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId);
              return name;
            });
        }
        private async static void CallerWithAsync()
        {
          Console.WriteLine("started CallerWithAsync in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId);
          string result = await GreetingAsync("Stephanie");
          Console.WriteLine(result);
          Console.WriteLine("finished GreetingAsync in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId);
        }
        
        private static void CallerWithContinuationTask()
        {
          Console.WriteLine("started CallerWithContinuationTask in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId);

          var t1 = GreetingAsync("Stephanie");


          t1.ContinueWith(t =>
            {
              string result = t.Result;
              Console.WriteLine(result);
              Console.WriteLine("finished CallerWithContinuationTask in thread {0} and task {1}", Thread.CurrentThread.ManagedThreadId, Task.CurrentId);
            });


        }

 



  使用async和await關鍵字,當await完成后,不需要進行任何處理,就能訪問UI線程。默認情況下,生成的代碼就會把線程轉換到擁有同步上下文的線程中。調用異步方法的線程分配給了同步上下文,await完成之后將繼續執行。
如果不使用相同的同步上下文,必須調用Task類的ConfigureAwait(false).例如,一個WPF應用程序,其await后面的代碼沒有任何用到UI元素的代碼。在這種情況下,避免切換到同步上下文會執行的更快。
  string s1 = await GreetingAsync("Stephanie").ConfigureAwait(false);

三.使用多個異步方法
  在一個異步方法里,可以調用一個或多個異步方法。如何編寫代碼,取決於一個異步方法的結果是否取決於另一個異步方法。
  1.按順序調用異步方法
  下面的例子,第第二個異步方法依賴於第一個異步方法的結果,await關鍵字就很有用。
  private async static void MultipleAsyncMethods()
  {
    string s1 = await GreetingAsync("Stephanie");
    string s2 = await GreetingAsync(s1);
    Console.WriteLine("Finished both methods.\n Result 1: {0}\n Result 2: {1}", s1, s2);
  }

  2.使用組合器
  如果第二個異步方法獨立於第一個,每個異步方法可以都不使用await,而是把每個異步方法的返回結果賦值給Task變量,就會運行的更快。
  private async static void MultipleAsyncMethodsWithCombinators1()
  {
    Task<string> t1 = GreetingAsync("Stephanie");
    Task<string> t2 = GreetingAsync("Matthias");
    await Task.WhenAll(t1, t2);
    Console.WriteLine("Finished both methods.\n Result 1: {0}\n Result 2: {1}", t1.Result, t2.Result);
  }
  Task.WhenAll組合器可以接受多個同一類型的參數,並返回同一類型的值。
  Task.WhenAll是在所有傳入方法的任務都完成了才返回Task。
  WhenAny是在其中一個傳入方法的任務完成了就返回。

  Task.WhenAll定義了幾個重載版本。如果所有的任務返回相同的類型,那么該類型的數組可用於await返回的結果。
  string[] result = await Task.WhenAll(t1, t2);

四.轉換異步模式
  http://www.cnblogs.com/afei-24/p/6757361.html講了三種異步編程的模式。
  並非所有的.NET Framework類在.NET 4.5中都引入了新的異步方法。還有許多類只提供類BeginXXX和EndXXX方法的異步模式,可以使用TaskFactory類的FromAsync方法,它可以把使用異步模式的方法轉換為基於任務的異步模式的方法。
  

/創建一個委托,並引用同步方法Greeting
        private static Func<string, string> greetingInvoker = Greeting;
        
        
        static IAsyncResult BeginGreeting(string name, AsyncCallback callback, object state)
        {
          return greetingInvoker.BeginInvoke(name, callback, state);
        }

        static string EndGreeting(IAsyncResult ar)
        {
          return greetingInvoker.EndInvoke(ar);
        }
        
        //FromAsync方法的前兩個參數是委托類型,傳入BeginGreeting, EndGreeting的地址。后兩個參數是輸入的參數和對象狀態參數。
        private static async void ConvertingAsyncPattern()
        {
          string r = await Task<string>.Factory.FromAsync<string>(BeginGreeting, EndGreeting, "Angela", null);
          Console.WriteLine(r);
        }

 

五.錯誤處理
  如果調用異步方法沒有等待,將異步方法放在try/catch中,就不捕獲不到異常。
  

private static void DontHandle()
        {
          try
          {
            ThrowAfter(200, "first");
            // exception is not caught because this method is finished before the exception is thrown
          }
          catch (Exception ex)
          {
            Console.WriteLine(ex.Message);
          }
        }
        
        static async Task ThrowAfter(int ms, string message)
        {
            Console.Write("xxx");
          await Task.Delay(ms);
          throw new Exception(message);
        }

  DontHandle方法調用ThrowAfter后,不會在該處等待,會繼續執行,不再保持對ThrowAfter方法的引用。
  注意:返回void的異步方法永遠不會等待.異步方法最好返回一個Task類型。

  1.異步方法的異常處理
  使用await關鍵字,將其放在在try/catch中
  

private static async void HandleOneError()
        {
          try
          {
            await ThrowAfter(2000, "first");
          }
          catch (Exception ex)
          {
            Console.WriteLine("handled {0}", ex.Message);
          }
        }

 

  2.多個異步方法的異常處理
  如果按照下面的代碼,第二個異常將不會拋出。因為第一個異常已經拋出,直接調到catch塊內了。
  

private static async void StartTwoTasks()
        {
          try
          {
            await ThrowAfter(2000, "first");
            await ThrowAfter(1000, "second"); // the second call is not invoked because the first method throws an exception
          }
          catch (Exception ex)
          {
            Console.WriteLine("handled {0}", ex.Message);
          }
        }

  使用Task.WhenAll,不管任務是否拋出異常,都會等到兩個任務完成。所以會拋出兩個異常。
  但是,只能看見傳遞給Task.WhenAll方法的第一個任務的異常信息,雖然第二個異常會拋出,但不會顯示:
  

private async static void StartTwoTasksParallel()
            {
              Task t1 = null;
              Task t2 = null;
              try
              {
                t1 = ThrowAfter(2000, "first");
                 t2 = ThrowAfter(1000, "second");
                await Task.WhenAll(t1, t2);
              }
              catch (Exception ex)
              {
                // just display the exception information of the first task that is awaited within WhenAll
                Console.WriteLine("handled {0}", ex.Message);
              }
            }

  3.使用AggregateException信息返回顯示異常
  將Task.WhenAll返回的結果寫到一個Task變量中,catch語句只檢索到第一個任務的異常,但可以訪問外部任務taskResult的Exception屬性。Exception屬性是AggregateException類型。這個異常類型定義了InnerExceptions屬性,它包含了等待中的所有異常的列表。
  

private static async void ShowAggregatedException()
        {
          Task taskResult = null;
          try
          {
            Task t1 = ThrowAfter(2000, "first");
            Task t2 = ThrowAfter(1000, "second");
            await (taskResult = Task.WhenAll(t1, t2));
          }
          catch (Exception ex)
          {
            // just display the exception information of the first task that is awaited within WhenAll
            Console.WriteLine("handled {0}", ex.Message);
            foreach (var ex1 in taskResult.Exception.InnerExceptions)
            {
              Console.WriteLine("inner exception {0} from task {1}", ex1.Message, ex1.Source);
            }
          }
        }

六.取消異步方法
  如果后台任務可能運行很長時間,就可能用的取消任務。
  取消框架基於協助行為,不是強制性的。一個運行時間很長的任務需要檢查自己是否被取消,在這種情況下,它的工作就是清理所有已打開的資源,並結束相關工作。
  取消基於CancellationTokenSource類,該類可用於發送取消請求。請求發送給引用CancellationToken類的任務,其中CancellationToken類和CancellationTokenSource相關聯。
  

private CancellationTokenSource cts = new CancellationTokenSource();
        
        //添加一個按鈕,用於取消正在運行的任務。使用cts.Cancel();
         private void button5_Click(object sender, EventArgs e)
         {
             if (cts != null)
                 cts.Cancel();
         }
         
        private async void button4_Click(object sender, EventArgs e)
        {
            
            string s =  await AsyncTaskTest();
        }

        //向Task類的Run方法傳遞CancellationToken參數。但需要檢查是否請求了取消操作。
         Task<string> AsyncTaskTest()
        {
            return Task.Run(() =>
                {
                cts.Token.ThrowIfCancellationRequested();
                    Thread.Sleep(5000);
                    return "異步完成";
                }
            , cts.Token);
        }

 



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM