Task.ContinueWith 和异步(核心 3.1)

Posted

技术标签:

【中文标题】Task.ContinueWith 和异步(核心 3.1)【英文标题】:Task.ContinueWith and async (core 3.1) 【发布时间】:2022-01-22 10:50:15 【问题描述】:

我的代码是这样的:

    public async Process()
    
        try
        
            var records = await this.provider.GetRecordsAsync(); // this method can throws 'DataStoreException' only
    
            foreach(var record in records)
            
                try
                
                    this.Do(record); // here is some exception and code goes to catch
                    record.Status = Status.Success;
                    await this.provider.UpdateAsync(record).ContinueWith(
                        task =>
                        
                            task.Exception?.Handle(e =>
                            
                                this.logger.LogError(e);
                                return true;
                            );
                        , TaskContinuationOptions.NotOnRanToCompletion);
                
                catch(Exception e)
                
                    record.Status = Status.Error;
/*line 106:*/           await this.provider.UpdateAsync(record).ContinueWith(
                        task =>
                        
                            task.Exception?.Handle(e =>
                            
                                this.logger.LogError(e);
                                return true;
                            );
                        , TaskContinuationOptions.NotOnRanToCompletion);
                
            
        
        catch (DataStoreException e)
        
            this.logger.LogError(e);
        
        catch (Exception e)
        
            // Got TaskCanceledException here
        
    

在评论点我得到了TaskCanceledException。我想原因是混合awaitContinueWith,但是有人可以解释一下,这里发生了什么? UpdateAsync 只能抛出 DataStoreException。所以TaskCanceledException thew by ContinueWith,但是没有取消令牌,代码甚至没有达到这一点task.Exception?.Handle

这是调用堆栈:

   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
   at ProjectTest.MyClass.<Process>d__6.MoveNext() in C:\Projects\ProjectTest\MyClass.cs:line 106

如果 try/catch 包围 this.provider.UpdateAsync(record) 而不是 ContinueWith 一切都很好并且代码按预期工作。 将awaitContinueWith 混合使用真的是不好的做法吗?如果是,为什么?

【问题讨论】:

附带说明一下,您熟悉finally 块吗? 除非您是专家并且有充分的理由,否则不要将ContinueWithasync-await 混为一谈。 【参考方案1】:

您看到的行为可以更简单地重现:

await Task.CompletedTask.ContinueWith(task =>
    
    , TaskContinuationOptions.NotOnRanToCompletion);

TaskContinuationOptions.NotOnRanToCompletion 指示ContinueWith 方法在任务成功完成时不执行回调,而是返回已取消的任务。

如果您有一个特别希望在发生错误时运行的延续,您将使用该选项。例如:

await Task.FromException(new Exception()).ContinueWith(task =>
    
        Console.WriteLine("Task did not execute to completion");
    , TaskContinuationOptions.NotOnRanToCompletion);

由于您认为this.provider.UpdateAsync(record) 可能会成功,因此这可能不是您想要使用的选项。

【讨论】:

问题的第二个目标——有没有关于ContinueWithawait的好帖子?我发现的都是肤浅的。附言当然我熟悉try/finally,上面的代码只是一个例子,实际代码有不同的异常处理:) @Jonik:我没有特别推荐的帖子。 Paulo 是对的:async/await 应该是您的默认设置。它不仅使您的代码更加惯用和直接:它在后台执行了许多编译魔术,以确保处理堆栈跟踪之类的事情比您实际处理的更好。如果您曾经做过非常复杂的事情,因此您需要手动管理任务延续,那么您需要阅读所有文档并了解 ContinueWith 等方法的来龙去脉。

以上是关于Task.ContinueWith 和异步(核心 3.1)的主要内容,如果未能解决你的问题,请参考以下文章

[C#]Task.ContinueWith使用实例

在 Task ContinueWith TaskScheduler.FromCurrentSynchronizationContext 的 ShowDialog 中打开表单时,应用程序会冻结

在单个异步方法中有多个等待的目的

C# Task 暂停与取消

8天玩转并行开发——第四天 同步机制(上)

iOS:核心数据异步数据块的获取和保存