簡體   English   中英

如何取消等待中的任務?

[英]How to cancel a Task in await?

我正在處理這些 Windows 8 WinRT 任務,並且我正在嘗試使用以下方法取消任務,並且它在某種程度上起作用。 CancelNotification 方法確實被調用了,這會讓你認為任務被取消了,但在后台任務一直在運行,然后任務完成后,任務的狀態總是完成並且永遠不會被取消。 有沒有辦法在取消任務時完全停止任務?

private async void TryTask()
{
    CancellationTokenSource source = new CancellationTokenSource();
    source.Token.Register(CancelNotification);
    source.CancelAfter(TimeSpan.FromSeconds(1));
    var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token);

    await task;            

    if (task.IsCompleted)
    {
        MessageDialog md = new MessageDialog(task.Result.ToString());
        await md.ShowAsync();
    }
    else
    {
        MessageDialog md = new MessageDialog("Uncompleted");
        await md.ShowAsync();
    }
}

private int slowFunc(int a, int b)
{
    string someString = string.Empty;
    for (int i = 0; i < 200000; i++)
    {
        someString += "a";
    }

    return a + b;
}

private void CancelNotification()
{
}

閱讀Cancellation (在 .NET 4.0 中引入,從那時起基本沒有變化)和Task-Based Asynchronous Pattern ,它提供了有關如何將CancellationTokenasync方法一起使用的指南。

總而言之,您將一個CancellationToken傳遞給每個支持取消的方法,並且該方法必須定期檢查它。

private async Task TryTask()
{
  CancellationTokenSource source = new CancellationTokenSource();
  source.CancelAfter(TimeSpan.FromSeconds(1));
  Task<int> task = Task.Run(() => slowFunc(1, 2, source.Token), source.Token);

  // (A canceled task will raise an exception when awaited).
  await task;
}

private int slowFunc(int a, int b, CancellationToken cancellationToken)
{
  string someString = string.Empty;
  for (int i = 0; i < 200000; i++)
  {
    someString += "a";
    if (i % 1000 == 0)
      cancellationToken.ThrowIfCancellationRequested();
  }

  return a + b;
}

或者,為了避免修改slowFunc (例如,假設您無權訪問源代碼):

var source = new CancellationTokenSource(); //original code
source.Token.Register(CancelNotification); //original code
source.CancelAfter(TimeSpan.FromSeconds(1)); //original code
var completionSource = new TaskCompletionSource<object>(); //New code
source.Token.Register(() => completionSource.TrySetCanceled()); //New code
var task = Task<int>.Factory.StartNew(() => slowFunc(1, 2), source.Token); //original code

//original code: await task;  
await Task.WhenAny(task, completionSource.Task); //New code

您還可以使用來自https://github.com/StephenCleary/AsyncEx 的不錯的擴展方法,讓它看起來像這樣簡單:

await Task.WhenAny(task, source.Token.AsTask());

一種尚未涉及的情況是如何在異步方法內處理取消。 舉個簡單的例子,你需要將一些數據上傳到服務,讓它計算一些東西,然后返回一些結果。

public async Task<Results> ProcessDataAsync(MyData data)
{
    var client = await GetClientAsync();
    await client.UploadDataAsync(data);
    await client.CalculateAsync();
    return await client.GetResultsAsync();
}

如果您想支持取消,那么最簡單的方法是傳入一個令牌並檢查它是否在每個異步方法調用之間被取消(或使用 ContinueWith)。 如果它們是長時間運行的呼叫,盡管您可能需要等待一段時間才能取消。 我創建了一個小助手方法,一旦取消就失敗。

public static class TaskExtensions
{
    public static async Task<T> WaitOrCancel<T>(this Task<T> task, CancellationToken token)
    {
        token.ThrowIfCancellationRequested();
        await Task.WhenAny(task, token.WhenCanceled());
        token.ThrowIfCancellationRequested();

        return await task;
    }

    public static Task WhenCanceled(this CancellationToken cancellationToken)
    {
        var tcs = new TaskCompletionSource<bool>();
        cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
        return tcs.Task;
    }
}

所以要使用它,只需將.WaitOrCancel(token)添加到任何異步調用中:

public async Task<Results> ProcessDataAsync(MyData data, CancellationToken token)
{
    Client client;
    try
    {
        client = await GetClientAsync().WaitOrCancel(token);
        await client.UploadDataAsync(data).WaitOrCancel(token);
        await client.CalculateAsync().WaitOrCancel(token);
        return await client.GetResultsAsync().WaitOrCancel(token);
    }
    catch (OperationCanceledException)
    {
        if (client != null)
            await client.CancelAsync();
        throw;
    }
}

請注意,這不會停止您正在等待的任務,它會繼續運行。 您需要使用不同的機制來停止它,例如示例中的CancelAsync調用,或者最好將相同的CancellationToken傳遞給Task以便它最終可以處理取消。 不建議嘗試中止線程。

我只想添加到已經接受的答案中。 我被困在這個問題上,但我在處理完整事件方面采用了不同的方法。 我沒有運行 await,而是向任務添加了一個已完成的處理程序。

Comments.AsAsyncAction().Completed += new AsyncActionCompletedHandler(CommentLoadComplete);

事件處理程序看起來像這樣

private void CommentLoadComplete(IAsyncAction sender, AsyncStatus status )
{
    if (status == AsyncStatus.Canceled)
    {
        return;
    }
    CommentsItemsControl.ItemsSource = Comments.Result;
    CommentScrollViewer.ScrollToVerticalOffset(0);
    CommentScrollViewer.Visibility = Visibility.Visible;
    CommentProgressRing.Visibility = Visibility.Collapsed;
}

使用此路由,所有處理都已為您完成,當任務被取消時,它只會觸發事件處理程序,您可以在那里查看它是否被取消。

暫無
暫無

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

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