简体   繁体   English

如何在.NET Core中执行多个任务,并检查哪个成功完成

[英]How to execute multiple tasks in .NET Core, and check which finished successfully

I am trying to perform the following scenario:我正在尝试执行以下场景:

  • Create multiple tasks创建多个任务
    • all tasks are in the same structure (same code with different parameters)所有任务都在相同的结构中(相同的代码具有不同的参数)
    • the structure is: try to do, catch if failed, and throw the exception / an exception结构是:尝试做,如果失败则捕获,并抛出异常/异常
  • Run them in parallel and wait for their completion并行运行它们并等待它们完成
  • After completion, check which tasks threw exception and which were succeeded, without the exception being thrown完成后,检查哪些任务抛出异常,哪些任务成功,没有抛出异常
public class Controller : ControllerBase
    {
        private readonly List<string> _names = new List<string>()
        {
            "name1",
            "name2"
        };

        [HttpGet]
        public async Task<ActionResult> Get()
        {
            // Leaving this processing because it is the same in the original code, maybe there is something here that is relevant
            var tasks = _names.ToDictionary(name => name, name => ExecuteRequest(name, async (value) =>
            {
                return await ReturnOrThrow(value);
            }));

            // I want to wait until all were finished
            await Task.WhenAll(tasks.Values); // This already throws an exception, i don't want it to
            // I don't want to catch exception here and just ignore it

            var namesThatSucceeded = tasks.Count(t => t.Value.IsCompletedSuccessfully);
            var namesThatThrewException = tasks.Count(t => t.Value.IsFaulted);

            return Ok(new
            {
                Succeeded = namesThatSucceeded,
                Failed = namesThatThrewException
            });
        }

        // The "generic task structure" that runs the request, catches exception if thrown, and re-throws it.
        private async Task<string> ExecuteRequest(string name, Func<string, Task<string>> request)
        {
            try
            {
                return await request(name);
            }
            catch (HttpRequestException e)
            {
                Console.WriteLine(e.Message);
                throw; // I would prefer to just return Faulted Task here so it won't throw exception
            }
        }

        // The actual processing
        private async Task<string> ReturnOrThrow(string name)
        {
            if (name == "name1")
            {
                throw new HttpRequestException();
            }

            return await Task.FromResult(name);
        }
    }

You can wrap action with a high order function which has await and exception handler.您可以使用具有等待和异常处理程序的高阶函数来包装动作。

 class Program
{
    static async Task Main(string[] args)
    {
        var itemsToProcess = new[] { "one", "two" };
        var results = itemsToProcess.ToDictionary(x => x, async (item) =>
        {
            try
            {
                var result = await DoAsync();
                return ((Exception)null, result);
            }
            catch (Exception ex)
            {
                return (ex, (object)null);
            }
        });

        await Task.WhenAll(results.Values);

        foreach(var item in results)
        {
            Console.WriteLine(item.Key + (await item.Value).Item1 != null ? " Failed" : "Succeed");
        }
    }

    public static async Task<object> DoAsync()
    {
        await Task.Delay(10);
        throw new InvalidOperationException();
    }
}

您是否在调用 ExecuteRequest 的 ToDictionary 中缺少等待?

async name => await ExecuteRequest

You cannot avoid exception throwing in awaiting for Task.WhenAll().您无法避免在等待 Task.WhenAll() 时抛出异常。 That's behavior by design.这是设计行为。 But you can pass Task's status via it's Result value.但是您可以通过它的 Result 值传递 Task 的状态。 Just extend the Result from string to (string Result, bool Success) and return false Success from catch without re-throwing Exception.只需将 Result 从字符串扩展到 (string Result, bool Success) 并从 catch 返回 false Success 而不重新抛出异常。

public class Controller : ControllerBase
{
    private readonly List<string> _names = new List<string>()
    {
        "name1",
        "name2"
    };

    [HttpGet]
    public async Task<ActionResult> Get()
    {
        // Leaving this processing because it is the same in the original code, maybe there is something here that is relevant
        var tasks = _names.ToDictionary(name => name, name => ExecuteRequest(name, async (value) =>
        {
            return await ReturnOrThrow(value);
        }));

        await Task.WhenAll(tasks.Values); // Doesn't throw exception anymore, but you can access Success status from Task's Result tuple

        var namesThatSucceeded = tasks.Count(t => t.Value.Result.Success);
        var namesThatThrewException = tasks.Count(t => !t.Value.Result.Success);

        return Ok(new
        {
            Succeeded = namesThatSucceeded,
            Failed = namesThatThrewException
        });
    }

    // The "generic task structure" that runs the request, catches exception if thrown, and re-throws it.
    private async Task<(string Result, bool Success)> ExecuteRequest(string name, Func<string, Task<string>> request)
    {
        try
        {
            return (await request(name), true);
        }
        catch (HttpRequestException e)
        {
            Console.WriteLine(e.Message);
            return (null, false);
        }
    }

    // The actual processing
    private async Task<string> ReturnOrThrow(string name)
    {
        if (name == "name1")
        {
            throw new HttpRequestException();
        }

        return await Task.FromResult(name);
    }
}

You're welcome.别客气。

An easy way to suppress the exception of an awaited task is to pass the task as a single argument to Task.WhenAny :抑制等待任务异常的一种简单方法是将任务作为单个参数传递给Task.WhenAny

Creates a task that will complete when any of the supplied tasks have completed.创建一个将在任何提供的任务完成后完成的任务。

await Task.WhenAny(Task.WhenAll(tasks.Values)); // Ignores the exception

This works because the task returned from Task.WhenAny never fails.这是有效的,因为从Task.WhenAny返回的任务永远不会失败。 When it completes, it is always completed successfully.当它完成时,它总是成功完成。 There are two small drawbacks though:但是有两个小缺点:

  1. It doesn't communicate clearly its intention, so adding a comment is advised.它没有清楚地传达其意图,因此建议添加评论。
  2. This method accepts a params Task[] argument, so calling it results in an object allocation (avoid using it in hot paths).此方法接受params Task[]参数,因此调用它会导致对象分配(避免在热路径中使用它)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM