2

我在CancellationTokenCancellationTokenSource周圍創建了一個小包裝。我的問題是CancelAsync方法CancellationHelper不能按預期方式工作。當我在異步方法中調用CancellationTokenSource的Cancel方法時,爲什麼不取消任務?

我遇到了ItShouldThrowAExceptionButStallsInstead方法的問題。要取消正在運行的任務,它調用await coordinator.CancelAsync();,但任務沒有真正取消和task.Wait

ItWorksWellAndThrowsException似乎運作良好不拋出異常,它使用coordinator.Cancel,這是不是一種異步方法在所有。

問題爲什麼當我在異步方法中調用CancellationTokenSource的Cancel方法時,任務未被取消?

不要讓waitHandle混淆你,它只是爲了不讓任務提前完成。

讓我們爲自己的代碼說話:

using System; 
using System.Collections.Generic; 
using System.Threading; 
using System.Threading.Tasks; 

namespace TestCancellation 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      ItWorksWellAndThrowsException(); 
      //ItShouldThrowAExceptionButStallsInstead(); 
     } 

     private static void ItShouldThrowAExceptionButStallsInstead() 
     { 
      Task.Run(async() => 
      { 
       var coordinator = new CancellationHelper(); 
       var waitHandle = new ManualResetEvent(false); 

       var task = Task.Run(() => 
       { 
        waitHandle.WaitOne(); 

        //this works well though - it throws 
        //coordinator.ThrowIfCancellationRequested(); 

       }, coordinator.Token); 

       await coordinator.CancelAsync(); 
       //waitHandle.Set(); -- with or without this it will throw 
       task.Wait(); 
      }).Wait(); 
     } 

     private static void ItWorksWellAndThrowsException() 
     { 
      Task.Run(() => 
      { 
       var coordinator = new CancellationHelper(); 
       var waitHandle = new ManualResetEvent(false); 

       var task = Task.Run(() => { waitHandle.WaitOne(); }, coordinator.Token); 

       coordinator.Cancel(); 
       task.Wait(); 
      }).Wait(); 
     } 
    } 

    public class CancellationHelper 
    { 
     private CancellationTokenSource cancellationTokenSource; 
     private readonly List<Task> tasksToAwait; 

     public CancellationHelper() 
     { 
      cancellationTokenSource = new CancellationTokenSource(); 
      tasksToAwait = new List<Task>(); 
     } 

     public CancellationToken Token 
     { 
      get { return cancellationTokenSource.Token; } 
     } 

     public void AwaitOnCancellation(Task task) 
     { 
      if (task == null) return; 

      tasksToAwait.Add(task); 
     } 

     public void Reset() 
     { 
      tasksToAwait.Clear(); 
      cancellationTokenSource = new CancellationTokenSource(); 
     } 

     public void ThrowIfCancellationRequested() 
     { 
      cancellationTokenSource.Token.ThrowIfCancellationRequested(); 
     } 

     public void Cancel() 
     { 
      cancellationTokenSource.Cancel(); 

      Task.WaitAll(tasksToAwait.ToArray()); 
     } 

     public async Task CancelAsync() 
     { 
      cancellationTokenSource.Cancel(); 

      try 
      { 
       await Task.WhenAll(tasksToAwait.ToArray()); 
      } 
      catch (AggregateException ex) 
      { 
       ex.Handle(p => p is OperationCanceledException); 
      } 
     } 
    } 
} 
+1

你在哪裏調用'CancellationHelper.ThrowIfCancellationRequested()'? –

+0

我相信對canellation token的工作方式存在誤解......即對於'Task.Run'這個令牌只有在任務建立時纔有意義,運行時必須自己檢查令牌... –

+0

@AndreasNiedermair I明白你在說什麼,但是如果任務完成,並且在執行過程中的某個時刻調用CancellationTokenSource.Cancel,那麼它的task.Result應該是TaskStatus.Canceled ''不應該嗎? – boli

回答

3

取消在.NET是合作。

這意味着,該一個保持CancellationTokenSource信號取消和該一個保持CancellationToken需要檢查取消是否被信號通知(或者通過輪詢CancellationToken或通過註冊的委託時,它用信號發送到運行)。

在您的Task.Run中,您使用CancellationToken作爲參數,但您不會在任務本身內檢查它,因此只有在任務有機會開始之前發送令牌時纔會取消該任務。

var task = Task.Run(() => 
{ 
    token.ThrowIfCancellationRequested(); 
}, token); 

在你的情況,你在ManualResetEvent塊,這樣你就不能檢查CancellationToken

要在它的運行,你需要檢查CancellationToken取消該任務。你可以註冊一個委託給解放了復位事件的CancellationToken

token.Register(() => waitHandle.Set()) 
+0

var task = Task.Run(()=> { token.ThrowIfCancellationRequested() ; },令牌);看起來不錯。但是在哪裏可以發現異常。 – swifty

相關問題