C# 数据块中的异常处理

C# 数据块中的异常处理,c#,multithreading,async-await,task-parallel-library,tpl-dataflow,C#,Multithreading,Async Await,Task Parallel Library,Tpl Dataflow,我试图理解TPL中的异常处理 以下代码似乎包含异常: var processor = new ActionBlock<int>((id) => SomeAction(id), new ExecutionDataflowBlockOptions { ... }); async Task SomeAction(int merchantID) { //Exception producing code ... } var processor=newactionbl

我试图理解TPL中的异常处理

以下代码似乎包含异常:

var processor = new ActionBlock<int>((id) => SomeAction(id), new ExecutionDataflowBlockOptions { ... });


async Task SomeAction(int merchantID)
{
    //Exception producing code
    ...
}
var processor=newactionblock((id)=>SomeAction(id),newexecutiondataflowblockoptions{…});
异步任务SomeAction(int-merchantID)
{
//异常生成代码
...
}
并侦听
任务调度器。未观察到的TaskException
事件也不会接收任何内容

那么,这是否意味着在运行操作时,操作块本身会执行try-catch


有关于这方面的官方文件吗?

更新

中解释了数据流块的异常处理行为

**原创的

此代码不接受异常。如果您使用
wait processor.Completion
等待块完成,您将得到异常。如果在调用
Complete()
之前使用循环将消息泵送到块,则也需要一种停止循环的方法。一种方法是使用
CancellationTokenSource
并在异常情况下发出信号:

void SomeAction(int i,CancellationTokenSource cts)
{
    try
    {
        ...
    }
    catch(Exception exc)
    {
        //Log the error then
        cts.Cancel();
       //Optionally throw
    }
}
发帖代码不需要改变那么多,只需要检查

var cts=new CancellationTokenSource();
var token=cts.Token;
var dopOptions=new new ExecutionDataflowBlockOptions { 
                           MaxDegreeOfParallelism=10,
                           CancellationToken=token
};
var block= new ActioBlock<int>(i=>SomeAction(i,cts),dopOptions);

while(!token.IsCancellationRequested && someCondition)
{
    block.Post(...);
}
block.Complete();
await block.Completion;
var cts=new CancellationTokenSource();
var-token=cts.token;
var dopOptions=新建ExecutionDataflowBlockOptions{
MaxDegreeOfParallelism=10,
取消令牌=令牌
};
var block=newactioblock(i=>SomeAction(i,cts),dopOptions);
而(!token.IsCancellationRequested&&someCondition)
{
block.Post(…);
}
block.Complete();
等待区块完成;
当动作抛出时,令牌将发出信号,块结束。如果异常被操作重试,那么它也将被
wait block.Completion
重试

如果这看起来很复杂,那是因为这有点像块的边缘情况。数据流用于创建块的管道或网络

一般情况

这个名字意义重大。 您没有使用相互调用的方法来构建程序,而是使用相互传递消息的处理块。没有父方法接收结果和异常。块管道保持活动状态以无限期地接收和处理消息,直到某个外部控制器告诉它停止,例如通过在头块上调用
Complete
,或向每个块发送取消令牌信号

块不应允许发生未处理的异常,即使它是独立的ActionBlock。如您所见,除非您已经调用了
Complete()
等待完成
,否则不会得到异常

当块内发生未经处理的异常时,块将进入故障状态。该状态将传播到与
PropagateCompletion
选项链接的所有下游块。上游块不受影响,这意味着它们可以继续工作,将消息存储在输出缓冲区中,直到进程耗尽内存,或者由于没有收到来自块的响应而死锁

正确的故障处理

该块应捕获异常,并根据应用程序的逻辑决定要执行的操作:

  • 记录它并继续处理。这与web应用程序的工作方式并没有太大区别——请求期间的异常不会导致服务器停机
  • 显式地向另一个块发送错误消息。这是可行的,但这种类型的硬编码不是很适合数据流
  • 使用带有某种错误指示器的消息类型。可能是
    成功
    标志,也可能是包含消息或错误的
    信封
    对象
  • 通过向用于生成所有块使用的
    CancellationToken
    CancellationTokenSource
    发送取消信号,优雅地取消整个管道。这相当于公共程序中的
    throw
  • #3是最通用的选项。下游块可以检查信封并忽略或传播失败的消息,而不进行处理。本质上,失败消息绕过下游块

    另一个选项是使用
    LinkTo
    中的
    谓词
    参数,将失败消息发送到记录器块,将成功消息发送到下一个下游块。在复杂的场景中,这可以用于重试某些操作并将结果发送到下游

    这些概念和图像来自Scott Wlaschin的

    事件不是处理故障任务异常的可靠/确定性方法,因为它会延迟,直到垃圾收集器清除故障任务。这可能发生在错误发生很久之后

    数据流块吞并的唯一异常类型是
    操作取消异常
    (由于未记录的原因,AFAIK)。所有其他异常都会导致块转换为故障状态。故障块的属性(即
    任务
    )也有故障(
    processor.Completion.IsFaulted==true
    )。您可以向
    Completion
    属性附加一个延续,以便在块失败时接收通知。例如,您可以通过简单地使流程崩溃来确保异常不会被忽略:

    processor.Completion.ContinueWith(t =>
    {
        ThreadPool.QueueUserWorkItem(_ => throw t.Exception);
    }, default, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
    
    这是因为在
    ThreadPool
    上引发未处理的异常会导致应用程序终止(在引发事件后)

    如果您的应用程序具有GUI(WinForms/WPF等),则可以在UI线程上引发异常,从而允许更优雅的错误处理:

    var uiContext = SynchronizationContext.Current;
    processor.Completion.ContinueWith(t =>
    {
        uiContext.Post(_ => throw t.Exception, null);
    }, default, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default);
    
    这将在WinForms中引发事件