【问题标题】:How to execute multiple tasks in .NET Core, and check which finished successfully如何在 .NET Core 中执行多个任务,并检查哪些任务成功完成
【发布时间】:2023-03-28 13:55:01
【问题描述】:

我正在尝试执行以下场景:

  • 创建多个任务
    • 所有任务都在相同的结构中(相同的代码,不同的参数)
    • 结构是:尝试做,如果失败则捕获,并抛出异常/异常
  • 并行运行它们并等待它们完成
  • 完成后,检查哪些任务抛出异常,哪些成功,没有抛出异常
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);
        }
    }

【问题讨论】:

  • 您看到的异常是什么?

标签: c# asp.net-core exception async-await task


【解决方案1】:

您可以使用具有等待和异常处理程序的高阶函数来包装操作。

 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();
    }
}

【讨论】:

    【解决方案2】:

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

    async name => await ExecuteRequest
    

    【讨论】:

      【解决方案3】:

      您无法避免在等待 Task.WhenAll() 时引发异常。这是设计的行为。 但是您可以通过它的 Result 值传递 Task 的状态。只需将 Result 从 string 扩展到 (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);
          }
      }
      

      不客气。

      【讨论】:

        【解决方案4】:

        抑制等待任务异常的一种简单方法是将任务作为单个参数传递给Task.WhenAny

        创建一个在任何提供的任务完成后完成的任务。

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

        这是可行的,因为从Task.WhenAny 返回的任务永远不会失败。当它完成时,它总是成功完成。不过有两个小缺点:

        1. 它没有清楚地传达其意图,因此建议添加评论。
        2. 此方法接受 params Task[] 参数,因此调用它会导致对象分配(避免在热路径中使用它)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-10-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-03-07
          • 1970-01-01
          相关资源
          最近更新 更多