[C#.NET][TPL] 任務取消通知

[C#.NET][TPL] Task Receive Information for Cancel

CancellationTokenSource 類別 來中斷任務的執行,這會用到帶有 CancellationToken 結構 參數的 Taks 類別建構函式

通知任務步驟:

  1. CancellationToken.IsCancellationRequested 屬性:判斷是否有調用 CancellationTokenSource.Cancel 方法
  2. CancellationToken.ThrowIfCancellationRequested 方法:拋出一個 OperationCanceledException 例外,並改變 Task.IsCanceled 屬性
  3. 利用 Task.ContinueWith 方法,調用子任務, 或是使用 CancellationToken.Register 方法,用來達到通知目的

利用 Task 的屬性觀察任務完成時的狀態

  1. IsCanceled:因取消而完成。
  2. IsCompleted:成功完成。
  3. IsFaulted:發生例外而完成。

更多的內容請參考:

http://msdn.microsoft.com/zh-tw/library/dd537607.aspx

 

內文章節:

判斷 CancellationToken.IsCancellationRequested 屬性:

調用 CancellationToken.ThrowIfCancellationRequested 方法:

調用 CancellationTokenRegistration 結構:

 


 

開始實作:

 

private SynchronizationContext m_SynchronizationContext;
private CancellationTokenSource m_cts;
private ulong m_Counter = 0;

public Form1()
{
    InitializeComponent();
    this.m_SynchronizationContext = SynchronizationContext.Current;
}

 

 

判斷 CancellationToken.IsCancellationRequested 屬性:

利用 IsCancellationRequested 屬性,得知是否有調用 Cancel 方法,進而離開任務,不過這樣做,並沒有真正的改變 CLR 對 mainTask 的狀態,我們可以藉由以下的觀察看出結果

 

private void DoWork9()
{
    var mainTask = new Task(() =>
    {
        while (true)
        {
            this.m_Counter++;

            if (this.m_cts.Token.IsCancellationRequested)
            {
                break;
            }

            //update UI
            m_SynchronizationContext.Post(a => { this.label1.Text = this.m_Counter.ToString(); }, null);
            SpinWait.SpinUntil(() => false, 100);
        }
    }, this.m_cts.Token);

    mainTask.Start();
    mainTask.ContinueWith(task =>
    {
        var status = string.Format("任務完成,完成狀態為:\rIsCanceled={0}\rIsCompleted={1}\rIsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted);
        MessageBox.Show(status);
    });
}

當我調用 DoWork9 方法時,m_Counter 就會不斷的累加,並把結果呈現在 label1

 

private void button_Start_Click(object sender, EventArgs e)
{
    this.m_cts = new CancellationTokenSource();
    DoWork9();
}

SNAGHTMLd39d0bc

 

當我調用 m_cts.Cancel 方法時,就會離開迴圈,並跳出 MessageBox,執行結果如下:

 

private void button_Stop_Click(object sender, EventArgs e)
{
    this.m_cts.Cancel();
}

 

由下圖得知,CLR 並沒有改變 mainTask 的 IsCanceled 狀態

SNAGHTMLd3a20f0

 

調用 CancellationToken.ThrowIfCancellationRequested 方法:

當調用 ThrowIfCancellationRequested 方法 時,可觀察出 CLR 改變了 mainTask 的 IsCanceled 屬性,CLR 不會把 ThrowIfCancellationRequested 方法當成是一個錯誤,而是協議式的取消,所以並沒有改變 IsFaulted 屬性。

PS.不能用除錯模式運行以下程式碼

private void DoWork10()
{
    var mainTask = new Task(() =>
    {
        while (true)
        {
            if (this.m_cts.Token.IsCancellationRequested)
            {
                this.m_cts.Token.ThrowIfCancellationRequested();
            }

            this.m_Counter++;

            //update UI
            m_SynchronizationContext.Post(_ => { this.label1.Text = this.m_Counter.ToString(); }, null);
            SpinWait.SpinUntil(() =>
            {
                if (this.m_cts.Token.IsCancellationRequested)
                {
                    this.m_cts.Token.ThrowIfCancellationRequested();
                }

                return false;
            }, 100);
        }
    }, this.m_cts.Token);

    mainTask.Start();
    mainTask.ContinueWith(task =>
    {
        try
        {
            var status = string.Format("任務完成,完成狀態為:\rIsCanceled={0}\rIsCompleted={1}\rIsFaulted={2}",
             task.IsCanceled,
             task.IsCompleted,
             task.IsFaulted);
            MessageBox.Show(status);
        }
        catch (AggregateException ex)
        {
            MessageBox.Show(ex.Message);
        }
    });
}

當我調用 DoWork10 方法時,m_Counter 就會不斷的累加,並把結果呈現在 label1

private void button_Start_Click(object sender, EventArgs e)
{
    this.m_cts = new CancellationTokenSource();
    DoWork10();
}

SNAGHTMLd3bff26

 

當我調用 m_cts.Cancel 方法時,就會離開迴圈,並跳出 MessageBox,執行結果如下:

 

private void button_Stop_Click(object sender, EventArgs e)
{
    this.m_cts.Cancel();
}

 

由下圖得知,CLR 改變了 mainTask 的 IsCanceled 狀態了

SNAGHTMLd3ce30d

 

 


若要更嚴僅,可在子任務加入 TaskContinuationOptions 列舉 參數;這表示,當 狀態條件 符合時,才會啟動子任務。

mainTask.ContinueWith(task =>
{
    try
    {
        var status = string.Format("任務完成,完成狀態為:\rIsCanceled={0}\rIsCompleted={1}\rIsFaulted={2}",
         task.IsCanceled,
         task.IsCompleted,
         task.IsFaulted);
        MessageBox.Show(status);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}, TaskContinuationOptions.OnlyOnCanceled);

 


調用 CancellationTokenRegistration 結構:

只要有註冊取消通知,一旦調用 CancellationTokenSource.Cancel 方法 後,就會執行註冊區塊。

this.m_cts.Token.Register(() => this.label1.Text = "任務取消");

 

private void DoWork11()
{
    var mainTask = new Task(() =>
    {
        while (true)
        {
            if (this.m_cts.Token.IsCancellationRequested)
            {
                this.m_cts.Token.ThrowIfCancellationRequested();
            }

            this.m_Counter++;

            //update UI
            m_SynchronizationContext.Post(_ => { this.label1.Text = this.m_Counter.ToString(); }, null);
            SpinWait.SpinUntil(() =>
            {
                if (this.m_cts.Token.IsCancellationRequested)
                {
                    this.m_cts.Token.ThrowIfCancellationRequested();
                }

                return false;
            }, 100);
        }
    }, this.m_cts.Token);

    mainTask.Start();
    mainTask.ContinueWith(task =>
    {
        try
        {
            //update UI
            this.m_cts.Token.Register(() =>
            {
                m_SynchronizationContext.Post(a => { this.label1.Text = "任務取消"; }, null);
            });
            var status = string.Format("任務完成,完成狀態為:\rIsCanceled={0}\rIsCompleted={1}\rIsFaulted={2}",
             task.IsCanceled,
             task.IsCompleted,
             task.IsFaulted);
            MessageBox.Show(status);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }, TaskContinuationOptions.OnlyOnCanceled);
}

 

當我調用 m_cts.Cancel 方法時,就會離開迴圈,並跳出 MessageBox,執行結果如下:

private void button_Stop_Click(object sender, EventArgs e)
{
    this.m_cts.Cancel();
}

SNAGHTMLd422500


以上出自:http://www.dotblogs.com.tw/yc421206/archive/2013/06/10/105434.aspx

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo