如何继续第一个成功的任务,如果所有任务都失败则抛出异常 [英] How to continue on first successful task, throw exception if all tasks fail

查看:20
本文介绍了如何继续第一个成功的任务,如果所有任务都失败则抛出异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在开发一个 Web API,它要求我执行几种不同的权限检查并执行异步操作,以查看是否允许用户进行 API 调用.如果用户只能通过其中一项检查,他们可能会继续,否则我需要抛出异常并将他们引导回 API,并返回 403 错误.

I am currently working on a web API that requires that I perform several different checks for rights and perform async operations to see if a user is allowed to make an API call. If the user can pass just one of the checks, they may continue, otherwise I need to throw an exception and boot them back out of the API with a 403 error.

我想做类似的事情:

public async Task<object> APIMethod()
{
    var tasks = new[] { CheckOne(), CheckTwo(), CheckThree() };

    // On first success, ignore the result of the rest of the tasks and continue
    // If none of them succeed, throw exception; 

    CoreBusinessLogic();
}

// Checks two and three are the same
public async Task CheckOne()
{
    var result = await PerformSomeCheckAsync();
    if (result == CustomStatusCode.Fail)
    {
        throw new Exception();
    }
} 

推荐答案

使用 Task.WhenAny 来跟踪任务完成并执行所需的逻辑.

Use Task.WhenAny to keep track of tasks as they complete and perform the desired logic.

下面的例子演示了解释的逻辑

The following example demonstrates the explained logic

public class Program {
    public static async Task Main() {
        Console.WriteLine("Hello World");
        await new Program().APIMethod();
    }

    public async Task APIMethod() {
        var cts = new CancellationTokenSource();
        var tasks = new[] { CheckOne(cts.Token), CheckTwo(cts.Token), CheckThree(cts.Token) };
        var failCount = 0;
        var runningTasks = tasks.ToList();            
        while (runningTasks.Count > 0) {
            //As tasks complete
            var finishedTask = await Task.WhenAny(runningTasks);
            //remove completed task
            runningTasks.Remove(finishedTask);
            Console.WriteLine($"ID={finishedTask.Id}, Result={finishedTask.Result}");
            //process task (in this case to check result)
            var result = await finishedTask;
            //perform desired logic
            if (result == CustomStatusCode.Success) { //On first Success                    
                cts.Cancel(); //ignore the result of the rest of the tasks 
                break; //and continue
            }
            failCount++;
        }

        // If none of them succeed, throw exception; 
        if (failCount == tasks.Length)
            throw new InvalidOperationException();

        //Core Business logic....
        foreach (var t in runningTasks) {
            Console.WriteLine($"ID={t.Id}, Result={t.Result}");
        }
    }

    public async Task<CustomStatusCode> CheckOne(CancellationToken cancellationToken) {
        await Task.Delay(1000); // mimic doing work
        if (cancellationToken.IsCancellationRequested)
            return CustomStatusCode.Canceled;
        return CustomStatusCode.Success;
    }

    public async Task<CustomStatusCode> CheckTwo(CancellationToken cancellationToken) {
        await Task.Delay(500); // mimic doing work
        if (cancellationToken.IsCancellationRequested)
            return CustomStatusCode.Canceled;
        return CustomStatusCode.Fail;
    }

    public async Task<CustomStatusCode> CheckThree(CancellationToken cancellationToken) {
        await Task.Delay(1500); // mimic doing work
        if (cancellationToken.IsCancellationRequested)
            return CustomStatusCode.Canceled;
        return CustomStatusCode.Fail;
    }
}

public enum CustomStatusCode {
    Fail,
    Success,
    Canceled
}

上面的例子产生以下输出

The above example produces the following output

Hello World
ID=1, Result=Fail
ID=2, Result=Success
ID=3, Result=Canceled

在示例中观察如何使用取消令牌来帮助取消在第一个成功任务完成时尚未完成的剩余任务.如果调用的任务设计正确,这有助于提高性能.

observe in the example how a cancellation token was used to help cancel the remaining tasks that have not completed as yet when the first successful task completed. This can help improve performance if the called tasks are designed correctly.

如果在您的示例中 PerformSomeCheckAsync 允许取消,那么应该利用它,因为一旦找到成功条件,就不再需要剩余的任务,那么让它们运行效率不高, 取决于他们的负载.

If in your example PerformSomeCheckAsync allows for cancellation, then it should be taken advantage of since, once a successful condition is found the remaining task are no longer needed, then leaving them running is not very efficient, depending on their load.

上面提供的例子可以聚合成一个可重用的扩展方法

The provided example above can be aggregated into a reusable extension method

public static class WhenAnyExtension {
    /// <summary>
    /// Continues on first successful task, throw exception if all tasks fail
    /// </summary>
    /// <typeparam name="TResult">The type of task result</typeparam>
    /// <param name="tasks">An IEnumerable<T> to return an element from.</param>
    /// <param name="predicate">A function to test each element for a condition.</param>
    /// <param name="cancellationToken"></param>
    /// <returns>The first result in the sequence that passes the test in the specified predicate function.</returns>
    public static async Task<TResult> WhenFirst<TResult>(this IEnumerable<Task<TResult>> tasks, Func<TResult, bool> predicate, CancellationToken cancellationToken = default(CancellationToken)) {
        var running = tasks.ToList();
        var taskCount = running.Count;
        var failCount = 0;
        var result = default(TResult);
        while (running.Count > 0) {
            if (cancellationToken.IsCancellationRequested) {
                result = await Task.FromCanceled<TResult>(cancellationToken);
                break;
            }
            var finished = await Task.WhenAny(running);
            running.Remove(finished);
            result = await finished;
            if (predicate(result)) {
                break;
            }
            failCount++;
        }
        // If none of them succeed, throw exception; 
        if (failCount == taskCount)
            throw new InvalidOperationException("No task result satisfies the condition in predicate");

        return result;
    }
}

将原始示例简化为

public static async Task Main()
{
    Console.WriteLine("Hello World");
    await new Program().APIMethod();
}

public async Task APIMethod()
{
    var cts = new CancellationTokenSource();
    var tasks = new[]{CheckThree(cts.Token), CheckTwo(cts.Token), CheckOne(cts.Token), CheckTwo(cts.Token), CheckThree(cts.Token)};
    //continue on first successful task, throw exception if all tasks fail
    await tasks.WhenFirst(result => result == CustomStatusCode.Success);
    cts.Cancel(); //cancel remaining tasks if any
    foreach (var t in tasks)
    {
        Console.WriteLine($"Id = {t.Id}, Result = {t.Result}");
    }
}

产生以下结果

Hello World
Id = 1, Result = Canceled
Id = 2, Result = Fail
Id = 3, Result = Success
Id = 4, Result = Fail
Id = 5, Result = Canceled

基于Check*函数

这篇关于如何继续第一个成功的任务,如果所有任务都失败则抛出异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆