C# .NET CORE: отменить другие задачи, если одна из них вернула какой-то результат

Как отменить все задачи, если одна из них возвращает, т.е. ложный (логический) результат? Можно ли определить, какая задача вернула результат?

class Program
    {
        private static Random _rnd = new Random();
        static void Main(string[] args)
        {
            var tasksCounter = _rnd.Next(4, 7);
            var cts = new CancellationTokenSource();

            var tasks = new Task<bool>[tasksCounter];
            for (int i = 0; i < tasks.Length; i++)
            {
                tasks[i] = CreateTask(cts); 
            }

            Console.WriteLine("Waiting..");
            Task.WaitAny(tasks);
            Console.WriteLine("Done!");

            Console.ReadKey();
        }

        private static Task<bool> CreateTask(CancellationTokenSource cts)
        {
            return Task.Factory.StartNew(TaskAction, cts.Token).Unwrap();
        }

        private static async Task<bool> TaskAction()
        {
            var delay = _rnd.Next(2, 5);
            await Task.Delay(delay * 1000);
            var taskResult = _rnd.Next(10) < 4;
            return await Task.FromResult(taskResult);
        }
    }

Я пытался использовать Task.WaitAll, Task.WaitAny и т. д., но ни один из этих методов не обеспечивает полезную (в моем случае) функциональность.


person ael    schedule 12.07.2019    source источник


Ответы (2)


Редактировать:
Как отметил @ckuri в комментариях, было бы проще использовать уже существующие свойства Task вместо того, чтобы писать собственный класс результатов. Соответственно скорректировал мой ответ.

Одним из решений было бы проверить результат в методе CreateTask() и передать CancellationToken в ваш метод TaskAction():

private static Task<bool> CreateTask(CancellationTokenSource cts)
{
    return Task.Run(async () =>
    {
        var result = await TaskAction(cts.Token);

        // If result is false, cancel all tasks
        if (!result)
            cts.Cancel();

        return result;
    });
}

private static async Task<bool> TaskAction(CancellationToken token)
{
    // Check for cancellation
    token.ThrowIfCancellationRequested();

    var delay = Rnd.Next(2, 5);
    // Pass the cancellation token to Task.Delay()
    await Task.Delay(delay * 1000, token);

    var taskResult = Rnd.Next(10) < 4;

    // Check for cancellation
    token.ThrowIfCancellationRequested();

    return taskResult;
}

Теперь вы можете сделать что-то подобное в своем методе Main, чтобы получить все задачи, которые не были отменены:

try
{
    // Wait for all tasks inside a try catch block because `WhenAll` throws a `AggregationException` 
    // containing a `TaskCanceledException`, if the token gets canceled
    await Task.WhenAll(tasks);
} 
catch { }

var tasksWithResult = tasks.Where(t => !t.IsCanceled).ToList();
person croxy    schedule 12.07.2019
comment
Задача уже имеет свойства Result и IsCanceled. Нет необходимости повторно реализовывать это. Вместо if (token.IsCancellationRequested) { … } простое token.ThrowIfCancellationRequested(); будет выполнять ту же работу и сделает ненужным try-catch. Это уменьшит ваш излишне сложный метод TaskAction до 5 строк. - person ckuri; 12.07.2019
comment
@ckuri Спасибо за подсказку, соответственно скорректировал мой ответ. - person croxy; 15.07.2019

У меня нет под рукой переводчика, так что извините за ошибки. Вашей задаче нужен доступ к токену, если вы хотите отменить все задачи из самой задачи. См. https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource?view=netcore-3.0 в качестве примера. т.е. передайте его в качестве аргумента задаче, чтобы одна задача могла отменить другие задачи.

    private static async Task<bool> TaskAction(CancellationTokenSource cts)
    {
        var delay = _rnd.Next(2, 5);
        await Task.Delay(delay * 1000);
        var taskResult = _rnd.Next(10) < 4;
        if (!taskResult)
           cts.Cancel()
        return await Task.FromResult(taskResult);
    }

Просто не забудьте захватить AggregateException и проверить, является ли одно из внутренних исключений TaskCanceledException.

person flindeberg    schedule 12.07.2019
comment
Вы устанавливаете CancellationTokens в отмененное состояние, но никогда ничего не делаете с токенами. Я думаю, вы забыли предоставить Task.Delay с CancellationToken? - person ckuri; 12.07.2019