Windows Forms 利用 Task 執行長時作業與中斷

  • 1960
  • 0
  • 2020-03-12

利用 Task 建立長時工作並可以取消的簡單範例

常見到一個問題『如果建立了一個長時的工作又要能取消,該怎麼做?』,我的建議是『若 Framework 的版本允許,使用 Task 來解決這問題是比較適當的做法』。

假設 Windows Forms 畫面上有兩個 Button 和一個 Label,button1 按下時會執行一個長時的程序,範例中是一直改變 Label.Text 屬性 ,按下button2 則取消這個程序,那該怎麼做?

首先,先為取消工作建立一個 CancellationTokenSource 型別的欄位,因為取消工作需要 CancellationToken。

 private CancellationTokenSource cts;

建立 button1.Click 事件的委派方法:

async private void button1_Click(object sender, EventArgs e)
{
    button1.Enabled = false;  
    cts = new CancellationTokenSource();
    int i = 0;
    try
    {
        await Task.Run (() =>
        {
            while (true)
            {
                this.Invoke(new Action(() => label1.Text = i.ToString()));
                i++;
                if (cts.Token.IsCancellationRequested)
                {
                    cts.Token.ThrowIfCancellationRequested();
                }
            }

        }, cts.Token
      );

    }
    catch (OperationCanceledException ex)
    {
        Debug.WriteLine("Task Canceled");
    }
    finally
    {
        button1.Enabled = true;
    }
}

在 button1.Click 事件的委派方法內先建立 CancellationTokenSource 的執行個體,使用 Task.Run(Func<Task> function, CancellationToken cancellationToken) 方法來執行長時工作,其中:

 () =>
 {
     while (true)
     {
         this.Invoke(new Action(() => label1.Text = i.ToString()));
         i++;
         if (cts.Token.IsCancellationRequested)
         {
             cts.Token.ThrowIfCancellationRequested();
         }
     }
 }

這一段 Lambda 就是要傳入給 Func<Task> function 的引數,而 cts.Token 則是要傳遞給 CancellationToken cancellationToken 的引數。在迴圈的最後加上的

if (cts.Token.IsCancellationRequested)
{
    cts.Token.ThrowIfCancellationRequested();
}

則是用於檢查工作是否已被取消,如果收到取消訊號則發出 OperationCanceledException (這就是為什麼需要 try catch 的原因)。

至於 button2 的處理就簡單多了,發出取消訊號就好:

 private void button2_Click(object sender, EventArgs e)
 {
     if (cts != null && !cts.IsCancellationRequested)
     {
         cts.Cancel();
     }
 }

很簡單的實作就可以解決類似的問題。