Вопрос по c# – Как отменить задачу в ожидании?

135

Я играю с этими задачами 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()
{
}
Только что нашелthis article что помогло мне понять различные способы отмены. Uwe Keim

Ваш Ответ

4   ответа
6

который не был рассмотрен, состоит в том, как обрабатывать отмену внутри асинхронного метода. Возьмем, к примеру, простой случай, когда вам нужно загрузить некоторые данные в сервис, заставить их вычислить что-то, а затем вернуть некоторые результаты.

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;
    }
}
Можешь пожалуйстаhelp me?
198

отмена (который был представлен в .NET 4.0 и с тех пор практически не изменился) иАсинхронный шаблон на основе задач, который предоставляет рекомендации о том, как использоватьCancellationToken сasync методы.

Подводя итог, вы передаете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;
}
Вау, отличная информация! Это сработало отлично, теперь мне нужно выяснить, как обрабатывать исключение в асинхронном методе. Спасибо чувак! Я прочту материал, который вы предложили. Carlo
Хорошо, обработка была легкой. Еще раз большое спасибо !! знак равно Carlo
Просто любопытно, есть ли причина, почему ни один из примеров не используетCancellationToken.IsCancellationRequested а вместо этого предлагаете бросать исключения?
Правильно. яrecommend что вы никогда не используетеWait или жеResult вasync методы; вы должны всегда использоватьawait вместо этого, который правильно разворачивает исключение.
Нет. Большинство длительных синхронных методов имеютsome способ отменить их - иногда закрывая основной ресурс или вызывая другой метод.CancellationToken имеет все хуки, необходимые для взаимодействия с пользовательскими системами отмены, но ничто не может отменить невозможный метод.
6

но я шел другим путем обработки полного события. Вместо запуска 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;
}

С этим маршрутом вся обработка уже сделана для вас, когда задача отменяется, она просто запускает обработчик события, и вы можете видеть, была ли она отменена там.

27

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());
Это выглядит очень сложно ... как реализация асинхронного ожидания. Я не думаю, что такие конструкции делают исходный код более читабельным.
Отличный ответ, спасибо.
Спасибо, одно замечание - регистрация токена должна быть позже утилизирована, второе - использованиеConfigureAwait в противном случае вы можете пострадать в приложениях пользовательского интерфейса.
На самом деле все, что нужно, это вложить его вusing.
@astrowalker: да, действительно, регистрация токена должна быть отменена (удалена). Это можно сделать внутри делегата, который передается в Register (), вызывая dispose для объекта, который возвращается Register (). Однако, поскольку "источник" токен в данном случае только локальный, все будет очищено в любом случае ...

Похожие вопросы