You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何线程安全地收集C# ActionBlock异步任务的执行结果?

两种线程安全收集任务结果的方法

这个问题我之前也碰到过!ActionBlock确实适合处理串行(或并行)的异步任务,但它的设计就是只执行操作不返回结果,所以要收集结果的话,有两种比较优雅的方式:

方法一:用TransformBlock替代ActionBlock(推荐,符合TPL DataFlow设计)

TransformBlock<TInput, TOutput>是ActionBlock的“升级版”,它既可以处理输入任务,还能返回处理后的结果,而且内部已经帮你处理好了线程安全的结果传递。

假设你的Process(int)方法返回Task<string>,示例代码如下:

// 定义TransformBlock:输入int,输出Process的结果类型
var transformBlock = new TransformBlock<int, string>(async i => await Process(i));

// 用BufferBlock来接收并存储结果,也可以直接后续处理
var resultsBuffer = new BufferBlock<string>();

// 链接两个块,设置PropagateCompletion让完成信号传递下去
transformBlock.LinkTo(resultsBuffer, new DataflowLinkOptions { PropagateCompletion = true });

// 发布任务
for(int i = 0; i < 100; i++)
{
    transformBlock.Post(i);
}

// 标记任务发布完成
transformBlock.Complete();
// 等待所有任务处理完毕
await transformBlock.Completion;

// 从BufferBlock收集所有结果
var finalResults = new List<string>();
while(resultsBuffer.TryReceive(out var singleResult))
{
    finalResults.Add(singleResult);
}

这种方法的好处是:

  • 完全遵循TPL DataFlow的组件设计,不需要自己手动处理线程安全
  • 默认会保持结果和输入的顺序一致(即使你设置了MaxDegreeOfParallelism并行处理,只要不修改TransformBlock的EnsureOrdered参数,结果顺序还是和输入对应)

方法二:保留ActionBlock,用线程安全集合收集结果

如果你不想替换现有ActionBlock,可以直接用.NET提供的线程安全集合来存储结果,比如ConcurrentBag<T>(专门为多线程添加场景设计,所有操作都是线程安全的)。

示例代码:

// 初始化线程安全的结果集合,替换成你的Process返回类型
var results = new ConcurrentBag<string>();

var actionBlock = new ActionBlock<int>(async i => 
{
    // 执行异步任务并获取结果
    var taskResult = await Process(i);
    // 直接添加到ConcurrentBag,无需额外锁
    results.Add(taskResult);
});

// 发布任务
for(int i = 0; i < 100; i++)
{
    actionBlock.Post(i);
}

actionBlock.Complete();
await actionBlock.Completion;

// 此时results中就是所有任务的结果,注意:如果ActionBlock设置了并行处理,结果顺序可能和发布顺序不一致

如果需要保持结果顺序,记得不要给ActionBlock设置MaxDegreeOfParallelism(默认是1,串行处理),或者改用方法一。

内容的提问来源于stack exchange,提问作者kiml42

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 12:07:39