2017-04-06 16 views
1

我似乎無法圍繞如何正確啓動任務並取消它。我已經改變了我的代碼,使其更易於理解我的問題並將其粘貼到下面(我還添加了一些評論)。首先調用SubscribeToReport方法 - 它包含一個await runReportTask,根據我的理解,它只會啓動runReportTask方法,等待它完成執行,然後繼續執行出現在await之後的代碼。如何正確啓動和取消任務。

Inside runReportTask我創建了一個CancellationTokenSource並使用Task.Factory.StartNew()開始一個新的任務。在任務內部,只要任務沒有被取消,就有一個while循環執行處理。根據我的理解,如果cancellationToken.IsCancellationRequested是真的,我的while循環中的其他內容將運行,任務將停止。之後,我期待SubscribeToReport()繼續執行 - 這種情況不會發生,並且在StopReportTask()中撥打cancellationTokenSource.Cancel()時,我的代碼似乎不會執行任何操作。我究竟做錯了什麼?我一直在這裏呆了一段時間,我似乎無法圍繞這應該如何工作。

private async static void SubscribeToReport() 
{ 
    Console.WriteLine("Waiting for task to finish or cancel..."); 

    await runReportTask(); 

    Console.WriteLine("Task has finished or was canceled."); // THIS LINE NEVER EXECUTES 
} 

private static CancellationTokenSource cancellationTokenSource; 
private static CancellationToken cancellationToken; 
private bool moreToDo = true; 
private static Task runReportTask() 
{ 
    cancellationTokenSource = new CancellationTokenSource(); 
    cancellationToken = cancellationTokenSource.Token; 

    return Task.Factory.StartNew(() => 
    { 
     // Were we already canceled? 
     cancellationToken.ThrowIfCancellationRequested(); 

     while(moreToDo == true) 
     { 
      if(!cancellationToken.IsCancellationRequested) 
      { 
       // Do important code here 
      } 
      else{ 
       Console.WriteLine("Task canceled."); // THIS LINE NEVER EXECUTES WHEN CALLING StopReportTask() below 
       cancellationToken.ThrowIfCancellationRequested(); 
       break; 
      } 
     } 

    }, cancellationToken); 
} 

// This method is called from another source file 
private void StopReportTask() 
{ 
    Console.WriteLine("Stopping report task..."); 

    cancellationTokenSource.Cancel(); 
} 
+0

你能否確認在同一個對象上調用'cancellationTokenSource.Cancel()'? – CodingYoshi

+0

包含此代碼的類是單例,所以是的。 – Roka545

+0

我開始認爲這可能是一個與我正在使用的nanomsg方法有關的問題。我正在使用nanomsg通過發佈者/訂閱者系統接收數據 - 在我的while循環中,我有一個偵聽消息的方法 - 它等待,直到它接收到一個 - 在這段時間內,我不認爲while循環繼續運行,解釋爲什麼取消令牌不起作用。我已經調試了我的StopReportTask方法,並且取消標記肯定會返回true(因爲它要取消該任務)。 – Roka545

回答

5

它包含的await runReportTask從我的理解會剛開始的runReportTask方法,等待它完成執行,然後恢復執行的await之後出現的代碼。

代碼:

await runReportTask(); 

是這樣的代碼:

var task = runReportTask(); 
await task; 

所以,要清楚,這是方法調用啓動任務運行。 await只是(異步)等待它完成。

同樣重要的是要注意await異步等待。這意味着它暫停該方法的執行並返回。所以,只要你的調用代碼知道,SubscribeToReport已經完成執行。

(這是problems of async void之一;它不容易消耗 - 調用代碼不知道它何時完成)。

裏面runReportTask我創建一個CancellationTokenSource並使用Task.Factory.StartNew()開始一個新的任務。

作爲便箋,您應該使用Task.RunTask.Factory.StartNew is a low-level API with dangerous default settings

它不會永遠看起來像我的代碼做任何事情,當我打電話cancellationTokenSource.Cancel()在StopReportTask()

這可能是一個不同的cancellationTokenSource實例。

你可以試試這個,作爲一個測試 - 我不知道如果這是你想要的語義,雖然(它會導致runReportTask取消以前的嘗試):

private static CancellationTokenSource cancellationTokenSource; 
private static readonly object _mutex = new object(); 
private bool moreToDo = true; 
private static Task runReportTask() 
{ 
    CancellationTokenSource oldCts, currentCts; 
    lock (_mutex) 
    { 
    oldCts = cancellationTokenSource; 
    currentCts = cancellationTokenSource = new CancellationTokenSource(); 
    } 
    if (oldCts != null) 
    oldCts.Cancel(); 
    var cancellationToken = currentCts.Token; 
    ... 
} 
2

這可能是輸入錯誤,但moreToDo不是靜態的。您的靜態功能RunReportTask無法訪問它。此外,您似乎希望runReportTask以外的其他人可以更改moreToDo以停止此任務。你確定你想要嗎?

如果您希望他人停止您的任務,爲什麼不讓他們使用CancellationTokenSource?

另一方面,如果您只希望您的RunReportTask是唯一一個更改moreToDo的人,爲什麼您要讓其他人訪問?

話雖如此,我真的不確定是否明智地讓你的程序是靜態的。你真的打算讓別人開始你的過程,讓其他人停止這個過程嗎?

另一個問題是,如果幾個初學者開始你的任務CancellationTokenSource被替換爲一個新的,而已經運行的任務仍然檢查舊的CancellationTokenSource創建的令牌,沒有人可以阻止舊的運行任務。你確定這是你的意圖嗎?

如果您強制任務啓動器提供CancellationTokenSource,則這些問題不會出現,這更像MSDN在文章Cancellation in managed threads中提出的。

如果您讓任務的創建者創建CancellationTokenSource,您將擁有正在運行的任務的所有者,他可以決定是否共享所有權。未經業主同意,其他人不能停止這項任務。

因爲您的SubscribeToReport返回void而不是Task我假設它是事件處理程序的簡化版本。如果沒有,我建議你讓它返回任務。

SubscribeToReport的非靜態版本會是這樣的:

public async Task SubscribeToReport(CancellationToken token) 
{ 
    Console.WriteLine("Waiting for task to finish or cancel..."); 
    await Task.Run(runReportTask(token), token); 
    Console.WriteLine("Task has finished or was canceled."); 
} 

private Task runReportTask(CancellationToken token) 
{ 
    bool moreToDo = false; 
    token.ThrowIfCancellationRequested(); 
    while(moreToDo && !Toke.IsCancellatinRequested) 
    { 
     // Do important code here, 
     // if this takes some time, make sure that 
     // you reqularly check IsCancellationRequested 
     // this part runs until!moreToDo 
    } 

    if (token.IsCancellationRequested) 
    { 
     Console.WriteLine("Task canceled."); 
     // do cleanup here 
    } 
} 

作爲替代方案:不檢查token.IsCancellationRequested,但撥打token.ThrowIfCancellationRequested。一定要在最後的陳述中進行清理。

用法:

using (var cancellationTokenSource = new CancellationTokenSource()) 
{ 
    var token = CancellationTokenSource.Token; 
    var task = SubscribeToReport(token); 
    // here you are free to do other things, 
    // if you decide to cancel: 
    cancellationTokenSource.Cancel(); 
    // or: 
    cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(5)); 

    // if you have nothing else to do but wait for the task to be completed 
    // or cancelled: 
    await task; 
    ProcessTaskResult(task); 
}   

注意,當你的任務完成了CancellationTokenSource整齊地佈置。 CancellationTokenSource的設計者實現了IDisposable,鼓勵您在垃圾收集器執行此操作之前儘快釋放所需的資源,只要您不再需要它們即可。