【问题标题】:How to observe tasks that are not awaited due to failure in another awaited task in C#?如何观察由于 C# 中另一个等待任务失败而未等待的任务?
【发布时间】:2020-08-11 08:20:16
【问题描述】:

我有这个代码:

var task1 = operation1();
var task2 = operation2();


var result1 = await task1;
var result2 = await task2;

我也处理UnobservedTaskException(通过记录它)。我面临的问题是,在 task1 失败并且第一次 await 导致异常后,task2 在错误中完成,然后我有一个我不想看到的日志条目,因为我已经记录了第一个异常,并且在那时所有后续我对例外不感兴趣。

所以我想知道是否有办法做某事,以便在我收到异常后以某种方式“忽略”所有任务。

我知道我可以使用await Task.WhenAll,但缺点是我必须等待所有异常发生,尽管我确信在第一个任务导致异常之后,我不需要等待另一个由于整个操作已经失败,因此要完成的任务。

另一种可能的解决方案是编写try/catch并取消所有任务,但这有点麻烦。

附: 该示例已简化,我确实有多个这样运行的任务。所以我正在寻找一种适用于任意数量任务的通用解决方案

【问题讨论】:

  • 最终,当你立即await 某事时,你就进入了“并发”领域——大概是有意为之。事情正在变得有点混乱:随之而来的是并发的选择。如果是我,我会确保 operation1()operation2() 永远不会出错 - 即他们应该在内部进行自己的异常处理;如果他们需要向调用者报告失败,他们可以通过 soft 错误(在返回值中 - 例如错误代码属性)
  • 这将是我们在整个堆栈中做的很多工作,例如很多http调用异步,所以很多管道都是这样做的,但这可能是最安全的,我只是希望.Net中有一些“灵丹妙药”。对于 async/await,这是相当普通的情况,但我猜 Task.WhenAll 是一种以一定成本解决它的标准方法
  • @IlyaChernomordik operation1operation2 是独立的吗?您希望如何顺序或并行运行它们?
  • 如果它们是独立的并且它们并行运行,那么你为什么要抑制一些失败? 1)两个都可以成功 2)两个都可以失败 3)其中一个可以失败。如果它们是独立的,那么只观察其中一个,那么日志可能会误导您。如果它们是耦合的,那么合作取消对我来说会更有意义。
  • 好吧,我想处理 UnobservedTaskException 以查看由于其他问题而发生的一些错误,例如我们忘记了在某个地方等待或发生了我们编码错误的任何其他事情。关于另一个问题:任务之间是相互独立的,但是当任何一个失败时,top request 都会失败,所以其他任务也失败了我们并不感兴趣,只要有一个例外就足够了。

标签: c# .net task task-parallel-library


【解决方案1】:

作为Task.WhenAll 的替代方案,您可以对Task.WhenAny 使用渐进式方法。

当任何任务完成时,您可以检查结果并决定下一步做什么。 (请记住,Task.WhenAny 即使等待它也不会抛出异常)这种方法的好处是您可以轻松地为此添加限制(控制并行度)。

渐进式异步的基本实现

static async Task ProgressiveAsyncForEach(int degreeOfParallelism, params Task[] tasks)
{
    var toBeProcessedTasks = new HashSet<Task>();
    var remainingTasksEnumerator = tasks.GetEnumerator();

    void AddNextTask()
    {
        if (!remainingTasksEnumerator.MoveNext()) return;
        var nextTaskToProcess = (Task)remainingTasksEnumerator.Current;
        toBeProcessedTasks.Add(nextTaskToProcess);
    }

    //Initialize
    while (toBeProcessedTasks.Count < degreeOfParallelism)
    {
        AddNextTask();
    }

    while (toBeProcessedTasks.Count > 0)
    {
        var processedTask = await Task.WhenAny(toBeProcessedTasks).ConfigureAwait(false);
        if (!processedTask.IsCompletedSuccessfully)
        {
            Console.WriteLine("One of the task has failed");
            //TODO: log first failed task
            //CONSIDER: cancel all the remaining tasks
            return;
        }

        toBeProcessedTasks.Remove(processedTask);
        AddNextTask();
    }
}

示例用法

static async Task Main(string[] args)
{
    await ProgressiveAsyncForEach(2, Faulty(), Fast(), Slow());
    Console.WriteLine("Application finished");
}

static async Task Slow()
{
    Console.WriteLine("Slow started");
    await Task.Delay(1000);
    Console.WriteLine("Slow finished");
}

static async Task Fast()
{
    Console.WriteLine("Fast started");
    await Task.Delay(500);
    Console.WriteLine("Fast finished");
}

static async Task Faulty()
{
    Console.WriteLine("Faulty started");
    await Task.Delay(700);
    throw new Exception();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-11
    • 2015-02-02
    • 1970-01-01
    • 2018-04-15
    • 2016-06-04
    相关资源
    最近更新 更多