Отмена асинхронных задач с семафорлимом занимает слишком много времени - PullRequest
1 голос
/ 26 апреля 2019

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

Очевидно, что .Run выбрасывает все задачи в .WaitAsync и поэтому имеет статус WaitingForActiviation. Таким образом, токен, данный самой задаче, фактически бессмыслен: все 1000 задач уже запущены.

Передача токена WaitAsync приводит к зависанию приложения при отмене.

static SemaphoreSlim batcher = new SemaphoreSlim(5);

        static void Main(string[] args)
        {            
            var tokenStore = new CancellationTokenSource();
            var tasks = Enumerable.Range(1, 1000).Select(i => 
                DoableWork(tokenStore.Token)
            ).ToList();

            do {
                if (Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Escape) {
                    Console.WriteLine("Cancelling tasks");
                    tokenStore.Cancel();
                }
                Thread.Sleep(1000);
                Console.WriteLine($"Tasks: {string.Join(", ", tasks.GroupBy(t => t.Status).Select(x => $"{x.Count()} {x.Key}"))}");
            } while (tasks.Any(t => t.Status < TaskStatus.RanToCompletion));
        }

        private static Task DoableWork(CancellationToken token)
        {
            return Task.Run(async () => {
                try {
                    await batcher.WaitAsync();
                    token.ThrowIfCancellationRequested();
                    await Task.Delay(200, token); // Do stuff
                } catch (OperationCanceledException) {
                    throw;
                } catch (Exception) {
                    // Logging
                    throw;
                } finally {
                    batcher.Release();
                }
            }, token);
        }

Результаты:

Tasks: 25 RanToCompletion, 975 WaitingForActivation
Tasks: 50 RanToCompletion, 950 WaitingForActivation
Tasks: 75 RanToCompletion, 925 WaitingForActivation
Tasks: 100 RanToCompletion, 900 WaitingForActivation
Tasks: 120 RanToCompletion, 880 WaitingForActivation
Tasks: 145 RanToCompletion, 855 WaitingForActivation
Cancelling tasks
Tasks: 145 RanToCompletion, 16 Canceled, 839 WaitingForActivation
Tasks: 145 RanToCompletion, 33 Canceled, 822 WaitingForActivation
Tasks: 145 RanToCompletion, 51 Canceled, 804 WaitingForActivation
Tasks: 145 RanToCompletion, 66 Canceled, 789 WaitingForActivation
Tasks: 145 RanToCompletion, 81 Canceled, 774 WaitingForActivation
Tasks: 145 RanToCompletion, 101 Canceled, 754 WaitingForActivation

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

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

tasks.Where(t => t.Status < TaskStatus.Running)
     .Take(5 - tasks.Count(t => t.Status == TaskStatus.Running))
     .ToList()
     .ForEach(t => t.Start());

private static Task DoableWork(CancellationToken token)
        {
            return new Task(async () => {
                try {
                    await Task.Delay(200, token); // Do stuff
                } catch (OperationCanceledException) {
                    throw;
                } catch (Exception) {
                    // Logging
                    throw;
                }
            }, token);
        }

1 Ответ

1 голос
/ 26 апреля 2019

Нашел проблему.Это VS 2019 (или одна из его настроек), которая значительно замедляет обработку исключений в режиме отладки.Когда я запускаю .exe напрямую, отмена происходит мгновенно.Спасибо @TheodorZoulias за неспособность воспроизвести проблему.

...