C# 如何在单个AggregateException中包装ActionBlock的所有异常
我遇到了TPLC# 如何在单个AggregateException中包装ActionBlock的所有异常,c#,exception,task-parallel-library,tpl-dataflow,C#,Exception,Task Parallel Library,Tpl Dataflow,我遇到了TPLActionBlock,它对于(限制的)并行异步操作似乎非常方便。到目前为止,我使用的是Task.WhenAll()(++信号量用于节流)。但在例外情况方面,似乎存在很大的差异: var successList = new List<int>(); var failedList = new List<int>(); try { var actionBlock = new ActionBlock<int>( async x
ActionBlock
,它对于(限制的)并行异步操作似乎非常方便。到目前为止,我使用的是Task.WhenAll()
(++信号量
用于节流)。但在例外情况方面,似乎存在很大的差异:
var successList = new List<int>();
var failedList = new List<int>();
try
{
var actionBlock = new ActionBlock<int>(
async x => await Task.Run(() =>
{
if (x < 5)
{
failedList.Add(x);
throw new Exception(x.ToString());
}
successList.Add(x);
}),
new ExecutionDataflowBlockOptions());
Enumerable.Range(1, 10).Each(x => actionBlock.Post(x));
actionBlock.Complete();
await actionBlock.Completion.ConfigureAwait(false);
}
catch (Exception ex)
{
// works for approach using task.whenall
Console.WriteLine(ex);
Assert.True(failedList.Count == 4);
Assert.True(successList.Count == 6);
return;
}
Assert.Fail();
将所有(!)异常包装到聚合异常中
但继续处理:
await Task.WhenAll(task1,task2).PreserveAllExceptions().ConfigureAwait(false);
使用ActionBlock
是否有一种简便的方法来实现这一点
更新:
澄清:
ExecutionDataflowBlockOptions
Task.Run()
仅用作实际异步函数的占位符任务.WhenAll()
和AggregateException的工作方式。
我知道我可以在我的ActionBlock
中尝试{}捕获{},并以某种方式存储异常,但我想知道是否有任何配置可能使这更容易。
总之,在我使用ActionBlock
的任何地方,只要使用try-catch并收集异常,都没什么大不了的。我只是发现Task.WhenAll()
+PreserveException
扩展对我来说更干净这个问题问什么还不清楚。但很明显,ActionBlock被滥用了。因为ActionBlock已经使用了一个或多个辅助任务,所以不需要
任务。运行。不需要信号量,因为ActionBlock(和其他块)已经通过限制工作任务和输入队列的数量来支持节流
代码似乎也试图将异常用作控制流机制,这在过程代码中也是错误的
异常并不意味着要避开阻塞。从通用的过程范式来看,没有函数相互调用,因此没有调用方来接收和处理异常
在数据流中,块以单个方向相互传递消息。块在管道或网络中组合,接收消息、处理消息并将其传递给任何连接的块。如果发生异常,则没有“调用方”可以接收该异常。未经处理的异常是灾难性的,会导致整个管道中断—不仅是单个块,还包括它链接到的所有下游块(如果PropagateCompletion
设置为true)。然而,上游区块永远不会知道这一点,从而导致意外情况
节流
使用ActionBlock进行节流很容易——对于初学者来说,所有块都只使用一个辅助任务。通过限制上游调用方的输入缓冲区并使用wait block.SendAsync()
而不是block.Post
,可以限制上游调用方。不需要任务。运行,因为块已使用辅助任务:
var options=new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism=2,
BoundedCapacity=2
};
var block =new ActionBlock<Message>(processMessage,options);
...
async Task processMessage(Message msg) { ...}
就这样。该块将同时处理2条消息(默认值仅为1条),并在其输入缓冲区中一次仅接受2条消息。如果缓冲区已满,则过帐循环将等待
通过在处理方法中添加延迟,可以实现快速和脏速率限制:
var block =new ActionBlock<Message>(msg=>{
await Task.Delay(200);
await processMessage(msg);
},options);
就这样。唯一的“诀窍”是谓词应该覆盖所有选项,否则消息将停留在块的输出缓冲区中。在所有其他链接之后使用默认链接有助于确保没有未处理的消息
错误处理
块不应允许异常转义。有几种错误处理策略取决于应用程序想要做什么
处理和记录
一种方法是处理它们并将其记录到位,就像处理web应用程序中的错误一样:
var block=新操作块(消息=>{
尝试
{
等待处理消息(msg);
}
捕获(异常exc)
{
_logger.LogError(exc,…);
}
},选项)
发布到另一个块
另一种可能是将异常和可能的有关传入消息的信息直接发布到另一个块。该块可以记录错误和消息,或者在延迟后重试。该块后面可能有一条不同的管道,用于在将消息发送到死信缓冲区之前重试延迟越来越大的消息,类似于对消息队列所做的操作:
var block =new ActionBlock<Message>(msg=>{
try
{
await processMessage(msg);
}
catch(SomeRetriableException exc)
{
_retryBlock.Post(new RetryMsg(msg,exc));
}
catch(Exception exc)
{
_logger.LogError(exc,....);
}
},options);
这个问题问什么还不清楚。但很明显,ActionBlock被滥用了。因为ActionBlock已经使用了一个或多个辅助任务,所以不需要任务。运行。不需要信号量,因为ActionBlock(和其他块)已经通过限制工作任务和输入队列的数量来支持节流
代码似乎也试图将异常用作控制流机制,这在过程代码中也是错误的
异常并不意味着要避开阻塞。从通用的过程范式来看,没有函数相互调用,因此没有调用方来接收和处理异常
在数据流中,块以单个方向相互传递消息。块在管道或网络中组合,接收消息、处理消息并将其传递给任何连接的块。如果发生异常,则没有“调用方”可以接收该异常。未经处理的异常是灾难性的,会导致整个管道中断—不仅是单个块,还包括它链接到的所有下游块(如果PropagateCompletion
设置为true)。然而,上游区块永远不会知道这一点,导致意外
var block =new ActionBlock<Message>(msg=>{
await Task.Delay(200);
await processMessage(msg);
},options);
var success=new BufferBlock<int>();
var failure=new BufferBlock<int>();
var block=new TransformBlock<Message,int>(...);
//Success if x>=5
block.LinkTo(success,x=>x>=5);
//By default, everything else goes to Failure
block.LinkTo(failure);
var block =new ActionBlock<Message>(msg=>{
try
{
await processMessage(msg);
}
catch(SomeRetriableException exc)
{
_retryBlock.Post(new RetryMsg(msg,exc));
}
catch(Exception exc)
{
_logger.LogError(exc,....);
}
},options);
class Envelope<T>
{
public T Message{get;}
public Exception Error {get;}
public Envelope (T msg)
{
Message=msg;
}
public Envelope(T msg,Exception err)
{
Message=msg;
Error=err;
}
}
var block=new TransformBlock<Envelope<Message>,Envelope<int>>(env=>{
try
{
var msg=env.Message;
....
return new Envelope(6);
}
catch(Exception exc)
{
return new Envelope(msg,exc);
}
});
var errorBlock = ActionBlock<Envelope<Message>>(...);
var success=new BufferBlock<int>();
var failure=new BufferBlock<int>();
//Send errors to `errorBlock`
block.LinkTo(errorBlock,env=>env.Error!=null);
//Success if x>=5
block.LinkTo(success,x=>x.Message>=5);
//By default, everything else goes to Failure
block.LinkTo(failure);
public class MyActionBlock<TInput> : ITargetBlock<TInput>
{
private readonly ActionBlock<TInput> _actionBlock;
private readonly ConcurrentQueue<Exception> _exceptions;
//...
public Task Completion
{
get
{
return _actionBlock.Completion.ContinueWith(t =>
{
if (_exceptions.Count > 0)
throw new AggregateException(_exceptions);
});
}
}
}