У меня есть класс Uploader с одним методом - Upload

public static int Upload(string endpoint,object objectToBeUploaded)
    {
        Source.Token.ThrowIfCancellationRequested();
        var repos = new UploadRepository(endpoint);
        return repos.Upload(objectToBeUploaded);
    }

Source - это статический CancellationTokenSource, доступный в проекте.

У меня также есть список конечных точек, для которых мне нужно загрузить определенный object.

Код в Form (это очень маленький проект, использующий WinForms) выглядит так:

private async Task UploadObjectAsync(
            string endpoint,
            object objectToBeUploaded)
     {
        try
        {
            int elementId  = await Task.Factory.StartNew(
                        () => Uploader.Upload(endpoint,objectToBeUploaded));
           //do something with the returned value..
        }
        catch(OperationCanceledEception ex)
        {
          //handle the exception..
        }
     }

Затем я устанавливаю обработчик btnUpload.Click таким образом, чтобы потом использовать его:

this.btnUpload.Click += async (s, e) =>
{
   foreach(var endpoint in endpoints)
   {
       await UploadObjectASsync(endpoint,someObject);
   }
}

Проблема в том, что всякий раз, когда я начинаю загрузку на все конечные точки (как они получаются, не имеет значения), и я решаю отменить процесс загрузки с помощью Source.Cancel();, первый UploadObjectAsync всегда будет выполняться, поскольку проверка Source.Token.ThrowIfCancellationRequested(); в методе Upload уже прошла. Остальные задачи будут отменены и обработаны корректно.

Как мне реструктурировать этот код, чтобы гарантировать, что первый UploadObjectAsync Task также будет отменен?

Стоит отметить, что у меня также нет доступа к исходному коду самого процесса загрузки (справка по сервису) - repos.Upload(objectToBeUploaded) в моем методе Upload.

0
pgerchev 1 Май 2016 в 13:21

3 ответа

Лучший ответ

Вам нужно сделать так, чтобы ваш UploadRepository.Upload принимал CancellationToken. Особенно, когда он выполняет операции ввода-вывода .. Вот тогда действительно окупается async/await.

Это также поможет вам избавиться от этого: Task.Factory.StartNew, поскольку метод Upload уже вернет Task. Не будет необходимости отделять задачу.

В ваших текущих настройках, если у вас будет достаточно времени для запуска задач (и прохождения ThrowIfCancellationRequested), вы не сможете отменить какую-либо загрузку. Даже если это займет 30 секунд.

Также вас могут заинтересовать: Task.Run

1
Bruno Garcia 1 Май 2016 в 11:40

Проблема в том, что вы не можете остановить процесс, который происходит внутри функции Upload, если он не проверит состояние CancellationToken, который не завершается сам.

Итак, что вы могли бы сделать, так это прервать выполнение потока, выполнив что-то вроде этого:

int elementId  = await Task.Factory.StartNew(() => 
{
  try
  {
    using (Source.Token.Register(Thread.CurrentThread.Interrupt))
    {
      return Uploader.Upload(endpoint, objectToBeUploaded));
    }
  }
  catch (ThreadInterruptedException ex)
  {
     throw new OperationCanceledEception(ex)
  }
}, Source.Token);

Используя функцию Source.Token.Register(delegate), вы заставляете токен вызывать эту функцию в случае, если токен аннулирован. Таким образом, поток, который в настоящее время выполняет загруженный файл, должен сразу же выбросить исключение.

Этот метод работает только в том случае, если поток время от времени входит в состояние WaitSleepJoin , потому что исключение возникает только в том случае, если поток находится в этом состоянии. Ознакомьтесь с документацией по Thread.Interrupt функция.

Альтернативой является использование Thread.Abort и { {X1}}. Это в любом случае убьет ваш поток, но может повредить внутреннее состояние вашей службы, потому что блокировки, удерживаемые потоком, не будут освобождены должным образом. Так что будьте очень осторожны при использовании этого метода.

0
Nitram 1 Май 2016 в 11:47

Вы не можете сделать ничего практичного. Метод загрузки не требует токена. Первая задача уже прошла проверку отмены к тому моменту, когда вы нажмете кнопку отмены. Вы можете доказать себе, что отмена - это проблема времени, добавив 10-секундный сон перед вызовом в случае отмены вызова. Тогда все задачи будут отменены.

1
Mike Barry 1 Май 2016 в 11:19