2010年6月15日 星期二

TPL 學習日記(3): 監看 task 的 Cancel

任務被 Cancel 後,我們需要一些進行一些處理,例如 UI 的顯示,使用者事件的記錄等。以下程式由上一次的範例繼續演變下去。

要監看任務是否被取消,有下列的方法。

方法1:使用Polling (輪詢)

這個方法即上次的演示,是在 Task body 內的迴圈中使用 tokenSource.IsCancellationRequested 來得到是否任務被取消。

方法2:使用Delegate

將程式重構,在建構子時建立 task,並在使用者按「start」鍵時啟始這個任務。

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;

namespace TPL_CancelAnApp
{
  public partial class MainWindow : Window
  {
    //建立 CancellationTokenSource
    CancellationTokenSource tokenSource = new CancellationTokenSource();
    Task task;
    public MainWindow()
    {
      InitializeComponent();
      InitialTask();
    }

    private void InitialTask()
    {
      task = new Task(() =>
      {
        long total = 0;
        while (total < 1000)
        {
          total++;
          Thread.Sleep(2);
          Action<string> action = new Action<string>(s => txtTotal.Text = s);
          txtTotal.Dispatcher.BeginInvoke(action, DispatcherPriority.Normal, total.ToString());
          //當收到要求取消任務時,我就終止任務
          tokenSource.Token.ThrowIfCancellationRequested();
        };
      }, tokenSource.Token); //具有這個 Token 的來源可以取消這個任務

      tokenSource.Token.Register(() => txtStatus.Text = "使用者要求停止。");
    }

    private void btnStart_Click(object sender, RoutedEventArgs e)
    {
      txtStatus.Text = "進行中";
      task.Start();
    }

    private void btnStop_Click(object sender, RoutedEventArgs e)
    {
      btnStart.IsEnabled = false;
      tokenSource.Cancel();
    }
  }
}
注意到在 InitialTask() 中,我增加了一行 tokenSource.Token.Register(…,這一行註冊了一個 delegate,當任務被取消時,會回過頭來呼叫該 delegate。
範例下載

方法3:使用Wait Handle

WaitHandle 是多執行緒程式中常用的物件,在TPL 中也不例外。在下例中,我建立了一個 task2,並且等到 task的Cancel呼叫後才會執行。

private void InitialTask()
{
  CancellationToken token = tokenSource.Token;
  task = new Task(() =>
  {
    long total = 0;
    …
  }, token); //具有這個 Token 的來源可以取消這個任務

  Task task2 = new Task(() =>
  {
    token.WaitHandle.WaitOne(); //直到 tokenSource.Cancel() 被呼叫後才會往下執行
    txtStatus.Dispatcher.BeginInvoke(new Action(() => txtStatus.Text = "使用者要求停止。"));
  });
  task2.Start();
}
範例下載

結論

雖然有三種方法可以監控任務的取消,但這三種方法都有不同的適用情境。第一種的輪詢方式適用於迴圈、第二種的Delegate 適用於簡單的一次性工作、而第三種則適用於多項任務的串連。

沒有留言:

Share with Facebook