簡體   English   中英

如何在C#中實現取消共享任務:s

[英]How to implement cancellation of shared Task:s in C#

所有這些都是關於在C#中取消任務的復雜案例的設計/最佳實踐的問題。 如何實現共享任務的取消?

作為一個最小的例子,我們假設以下內容; 我們有一個長期運行,可合作取消的“工作”操作。 它接受取消令牌作為參數,如果已被取消則拋出。 它在某個應用程序狀態下運行並返回一個值。 其結果由兩個UI組件獨立完成。

雖然應用程序狀態不變,但應該緩存Work函數的值,如果一個計算正在進行,則新請求不應該開始第二次計算,而是開始等待結果。

任何一個UI組件都應該能夠取消它的任務,而不會影響其他UI組件任務。

你到目前為止和我在一起嗎?

上面的內容可以通過在TaskCompletionSources中引入一個包裝真實Work任務的Task緩存來完成,其任務:然后返回給UI組件。 如果UI組件取消了它的Task,它只會放棄TaskCompletionSource Task而不是底層任務。 這一切都很好。 UI組件創建CancellationSource,取消請求是正常的自頂向下設計,底部是協作TaskCompletionSource任務。

現在,到了真正的問題。 應用程序狀態更改時該怎么辦? 讓我們假設讓“工作”功能在狀態副本上運行是不可行的。

一種解決方案是監聽任務緩存中的狀態變化(或那里)。 如果緩存具有底層任務使用的CancellationToken,即運行Work函數的任務,則可以取消它。 然后,這可以觸發取消所有附加的TaskCompletionSources Task:s,因此兩個UI組件都將獲得Canceled任務。 這是一種自下而上的取消。

有沒有一種首選的方法呢? 是否有一種設計模式將其描述在哪里?

可以實現自下而上取消,但感覺有點奇怪。 UI任務是使用CancellationToken創建的,但由於另一個(內部)CancellationToken而被取消。 此外,由於令牌不相同,因此不能在UI中忽略OperationCancelledException - 這會(最終)導致在外部Task:s終結器中拋出異常。

聽起來你想要一組貪婪的任務操作 - 你有一個任務結果提供者,然后構造一個任務集來返回第一個完成的操作,例如:

// Task Provider - basically, construct your first call as appropriate, and then 
//   invoke this on state change

public void OnStateChanged()
{
    if(_cts != null)
       _cts.Cancel();

    _cts = new CancellationTokenSource();
    _task = Task.Factory.StartNew(() =>
       {
           // Do Computation, checking for cts.IsCancellationRequested, etc
           return result;
       });
}

// Consumer 1

var cts = new CancellationTokenSource();
var task = Task.Factory.StartNew(() =>
  {
       var waitForResultTask = Task.Factory.StartNew(() =>
          {
              // Internally, this is invoking the task and waiting for it's value
              return MyApplicationState.GetComputedValue();
          });

       // Note this task cares about being cancelled, not the one above
       var cancelWaitTask = Task.Factory.StartNew(() =>
         {
              while(!cts.IsCancellationRequested)
                 Thread.Sleep(25);

              return someDummyValue;
         });

       Task.WaitAny(waitForResultTask, cancelWaitTask);

       if(cancelWaitTask.IsComplete)
          return "Blah"; // I cancelled waiting on the original task, even though it is still waiting for it's response
       else
          return waitForResultTask.Result;
  });

現在,我還沒有對此進行全面測試,但它應該允許您通過取消令牌來“取消”等待任務(從而強制“等待”任務先完成並點擊WaitAny ),並允許您“取消“計算任務。

另一件事是想出一個干凈的方法讓“取消”任務等待沒有可怕的阻止。 我認為這是一個好的開始。

這是我的嘗試:

// the Task for the current application state
Task<Result> _task;
// a CancellationTokenSource for the current application state
CancellationTokenSource _cts;

// called when the application state changes
void OnStateChange()
{
    // cancel the Task for the old application state
    if (_cts != null)
    {
        _cts.Cancel();
    }

    // new CancellationTokenSource for the new application state
    _cts = new CancellationTokenSource();
    // start the Task for the new application state
    _task = Task.Factory.StartNew<Result>(() => { ... }, _cts.Token);
}

// called by UI component
Task<Result> ComputeResultAsync(CancellationToken cancellationToken)
{
    var task = _task;
    if (cancellationToken.CanBeCanceled && !task.IsCompleted)
    {
        task = WrapTaskForCancellation(cancellationToken, task);
    }
    return task;
}

static Task<T> WrapTaskForCancellation<T>(
    CancellationToken cancellationToken, Task<T> task)
{
    var tcs = new TaskCompletionSource<T>();
    if (cancellationToken.IsCancellationRequested)
    {
        tcs.TrySetCanceled();
    }
    else
    {
        cancellationToken.Register(() =>
        {
            tcs.TrySetCanceled();
        });
        task.ContinueWith(antecedent =>
        {
            if (antecedent.IsFaulted)
            {
                tcs.TrySetException(antecedent.Exception.GetBaseException());
            }
            else if (antecedent.IsCanceled)
            {
                tcs.TrySetCanceled();
            }
            else
            {
                tcs.TrySetResult(antecedent.Result);
            }
        }, TaskContinuationOptions.ExecuteSynchronously);
    }
    return tcs.Task;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM