文章出處

從一個簡單的異步操作開始

  System.Console.WriteLine("主線程:開啟一個異步操作...");
  ThreadPool.QueueUserWorkItem((o) => {
      for (int i = 0; i < 100; i++)
      {
          System.Console.WriteLine("異步操作:{0}",i);
          Thread.Sleep(100);
      }
  });
  System.Console.WriteLine("主線程:其他任務...");
  Thread.Sleep(1000);//模擬其他任務
  System.Console.WriteLine("主線程任務完成");

執行上下文

每個線程都關聯了一個執行上下文的數據結構。執行上下文包括:安全設置、宿主設置以及邏輯上下文數據。
線程執行代碼時,有的操作會受到線程的執行上下文的影響。理想情況下,每一個線程(初始線程)使用另一個線程(輔助線程)執行任務時,前者執行上下文應該流向(復制到)輔助線程。確保輔助線程執行的任何操作使用的是相同的安全設置和宿主設置,初始線程的邏輯調用上下文可以在輔助線程中使用。
默認情況下,CLR自動將初始線程上下文復制到輔助線程中,這樣會對性能造成一定的影響,尤其是這些數據沒有用的時候,為了避免這種情況,可以使用ExecutionContext類阻止執行上下文流動。

  CallContext.LogicalSetData("Name", "Jeffrey");
  ThreadPool.QueueUserWorkItem((o) =>
  {
      System.Console.WriteLine("Name:{0}", CallContext.LogicalGetData("Name"));
  });
  //阻止當前線程上下文流動
  ExecutionContext.SuppressFlow();
  ThreadPool.QueueUserWorkItem((o) =>
  {
      System.Console.WriteLine("Name:{0}", CallContext.LogicalGetData("Name"));
  });

協作式取消

在進行耗時操作的時候,如果我們想取消,且不然代碼繼續執行,是否可以實現?答案當然是可以的。為了取消一個操作,首先必須創建一個System.Threading.CancellationTokenSource對象。

  static void Main(string[] args)
  {
      CancellationTokenSource cts = new CancellationTokenSource();
      ThreadPool.QueueUserWorkItem(o=>Count(cts.Token,1000));
      System.Console.WriteLine("Press <Enter> to Cancel opreation");
      System.Console.ReadLine();
      cts.Cancel();//如果Count方法已經返回,Cancel沒有任何效果

      System.Console.ReadKey();
  }

  private static void Count(CancellationToken token, int v)
  {
      for (int i = 0; i < v; i++)
      {
          if (token.IsCancellationRequested)
          {
              System.Console.WriteLine("Count is Canceled");
              break;
          }
          System.Console.WriteLine(i);
          Thread.Sleep(200);
      }
      System.Console.WriteLine("Count is done");
  }

任務

雖然通過ThreadPool.QueueUserWorkItem()方法可以執行異步操作,但是它依然有一些局限,比如無法知道異步操作何時完成,獲取返回值。為了解決這些問題,微軟提供了Task類。

等待任務完成并獲取結果

  Task<int> task = new Task<int>(() =>
     {
         int sum = 0, t = 1000000;
         for (; t > 0; t--)
         {
             sum += t;
         }
         return sum;
     });
  //可以指定啟動的時機
  task.Start();
  //可以顯示等待任務完成
  task.Wait();
  //獲取結果(Result內部會執行Wait方法)
  System.Console.WriteLine(task.Result);

取消任務

 var cts = new CancellationTokenSource();
 Task<int> task = new Task<int>(() => Count(cts.Token, 100), cts.Token);
 //可以指定啟動的時機
 task.Start();

 //在某個時刻,調用取消CancellationTokenSource以取消任務
 Thread.Sleep(500);
 cts.Cancel();
  private static int Count(CancellationToken token, int v)
  {
      int r = 0;
      for (int i = 0; i < v; i++)
      {
          if (token.IsCancellationRequested)
          {
              r = i;
              System.Console.WriteLine("Count is Canceled");
              break;
          }
          System.Console.WriteLine(i);
          Thread.Sleep(200);
      }
      System.Console.WriteLine("Count is done");
      return r;
  }

一個任務完成時自動啟動一個新任務

 Task<int> task = new Task<int>(() =>
 {
     Thread.Sleep(1000);
     return 0;
 });
 task.Start();
 task.ContinueWith(t => System.Console.WriteLine(t.Result));

任務工廠

當需要創建一組Task對象來共享相同的狀態,為了避免將相同的參數一個個傳遞給Task的構造器,可以通過創建一個任務工廠封裝通用的狀態。
TaskCreationOptions:指定用于控制任務的創建和執行的可選行為的標志。
TaskContinuationOptions:為通過使用 Task.ContinueWithTask<TResult>.ContinueWith 方法創建的任務指定行為。此枚舉有一個 FlagsAttribute 特性,通過該特性可使其成員值按位組合。
TaskScheduler:表示一個處理將任務排隊到線程中的低級工作的對象。

  static void Main(string[] args)
  {
      Task parent = new Task(() => {
          var cts = new CancellationTokenSource();
          var tf = new TaskFactory<int>(cts.Token, TaskCreationOptions.AttachedToParent, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);

          var childTasks = new[] {
          tf.StartNew(()=>Sum(cts.Token,10000)),
          tf.StartNew(()=>Sum(cts.Token,1500)),
          tf.StartNew(()=>Sum(cts.Token,int.MaxValue))
      };

          for (int i = 0; i < childTasks.Length; i++)
          {
              childTasks[i].ContinueWith(t => cts.Cancel(), TaskContinuationOptions.NotOnFaulted);
          }

          tf.ContinueWhenAll(
              childTasks,
              c => c.Where(w => !w.IsFaulted && !w.IsCanceled).Max(m => m.Result),
              CancellationToken.None).
              ContinueWith(c => Console.WriteLine("Max:{0}", c.Result), TaskContinuationOptions.ExecuteSynchronously);
      });

      parent.ContinueWith(c => {
          StringBuilder sb = new StringBuilder("Exceptions :" + Environment.NewLine);
          foreach (var item in c.Exception.Flatten().InnerExceptions)
          {
              sb.AppendLine(" "+item.GetType().ToString());
          }
      },TaskContinuationOptions.OnlyOnFaulted);

      parent.Start();
      Console.ReadKey();

  }

  private static int Sum(CancellationToken token, int v)
  {
      int returnValue = 0;
      for (int i = 0; i < v; i++)
      {
          //在取消標志引用的CancellationTokenSource上,如果調用Cancel,ThrowIfCancellationRequested()方法拋出異常
          token.ThrowIfCancellationRequested();

          //if (token.IsCancellationRequested)
          //{
          //    break;
          //}
          returnValue += i;
      }
      return returnValue;
  }

Parallel的靜態For,ForEach和Invoke方法

Parallel靜態類的內部是通過Task實現的。Parallel的方法可以并行執行任務。他封裝了任務在某些情況下的編碼簡便方法。

  //線程池的線程并行處理
  Parallel.For(0, 100, i => DoWork(i));
  //ForEach的效率要比For差一些
  Parallel.ForEach(collection, item => DoWork(item));
  Parallel.Invoke(() => DoWork1(), DoWork2());

Parallel本身的創建也是會有一定的開銷,如果只是為了完成幾項非耗時的方法,不需要通過Parallel來完成。
Parallel的循環方法中,如果有其中一個方法拋出了異常,Parallel方法最后會拋出一個AggregateException
Parallel方法內執行的函數要避免修改共享數據,Parallel的并發性容易造成數據狀態異常,當然也可以加同步線程鎖來控制,但是這樣Parallel的并發執行任務就沒有效果了。

計時器

System.Threading.Timer:如果要在一個線程池上執行定時的(周期性發生的)后臺任務時,最好選用它。
System.Windows.Forms.Timer(System.Windows.Threading.DispatcherTimer):調用win32的SetTimer函數,所有工作只由一個線程完成,所以它不會并發完成任務。
System.Timers.Timer:Jeffrey Richter說這是微軟頭昏腦脹的時候搞出來的一個類,除非你在設計平面上想用一個計時器。
一般計時器都會遇到這個問題:比如設置的計時器是每秒調用一次方法,但是方法執行比較耗時,超過了一秒,這個時候就會遇到同時再執行同一個方法,為了避免這種問題

   static Timer _timer;
   static void Main(string[] args)
   {
       //Timeout.Infinite=-1;表示時鐘只執行一次
       using (_timer = new Timer(ProcessCallback, 5, 0, Timeout.Infinite))
       {
           Console.WriteLine("Do SomeThing...");
           Thread.Sleep(10000);
       }
       Console.ReadKey();
   }

   private static void ProcessCallback(object state)
   {
       Console.WriteLine("Process:{0}", state);
       Thread.Sleep(1000);//模擬耗時操作
       //繼續調用
       _timer.Change(2000, Timeout.Infinite);
   }

上面的這段代碼,到了10秒周_timer.Change()會引發對象已經釋放掉的異常(使用using),大家可以通過_timer.Dispose()方法釋放_timer的資源


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()