【问题标题】:TPL: Dispose processed itemsTPL:处置已处理的物品
【发布时间】:2022-02-01 02:21:04
【问题描述】:

在 C# 中,我使用Task Parallel Library (TPL) 来下载图像、处理图像并保存分析结果。简化代码如下。

var getImage = new TransformBlock<int, Image>(GetImage);
var proImage = new TransformBlock<Image, double>(ProcessImage);
var saveRes = new ActionBlock<double>(SaveResult);

var linkOptions = new DataflowLinkOptions() { PropagateCompletion = true };

getImage.LinkTo(proImage, linkOptions);
proImage.LinkTo(SaveRes, linkOptions);

for (int x = 0; x < 1000000; x++)
    getImage.Post(x);
getImage.Complete();

SaveRes.Completion.Wait();

这按预期工作,但内存使用情况除外。我期待int_ximage_xdouble_x 在管道处理完该迭代后被释放。换句话说,我希望在执行getImageproImagesaveRes 期间创建的每个资源用于迭代x 在最后一个块完成执行时被释放。但是,此实现将所有对象保留在内存中,直到我退出 TPL 范围。

我错过了什么吗?这是 TPL 的预期行为吗?是否有任何选项可以设置,以便在每次迭代结束时释放资源?

更新

按照 cmets 中的建议,我使用 BufferBlockSendAsync 重写了代码,如下所示。但是,我认为这不会导致要求每个任务消耗的资源。设置BoundedCapacity 只会导致我的程序在我认为它已达到BoundedCapacity 设置的限制时停止。

var blockOpts = new DataflowBlockOptions()
{ BoundedCapacity = 100 };

var imgBuffer = new BufferBlock<int>(blockOpts);

var getImage = new TransformBlock<int, Image>(GetImage, blockOpts);
var proImage = new TransformBlock<Image, double>(ProcessImage, blockOpts);
var SaveRes = new ActionBlock<double>(SaveResult, blockOpts);

var linkOptions = new DataflowLinkOptions() { PropagateCompletion = true };

imgBuffer.LinkTo(getImage, linkOptions);
getImage.LinkTo(proImage, linkOptions);
proImage.LinkTo(SaveRes, linkOptions);

for (int x = 0; x < 1000000; x++)
    await imgBuffer.SendAsync(x);
getImage.Complete();

SaveRes.Completion.Wait();

【问题讨论】:

  • 您是否尝试过使用合理的BoundedCapacity 配置数据流块?这是一个相关的问题:TPL Dataflow - very fast producer, not so fast consumers OutOfMemory exception.
  • 如果你使用BoundedCapacity配置,而ProcessImage/SaveResult方法会抛出异常,建议你看看这个问题:TPL Dataflow exception in transform block with bounded capacity
  • Hamed 是的,如果您省略处置 IDisposable 对象,则不会处置该对象。它持有的资源直到被垃圾回收器最终确定后才会被释放(假设所有对该对象的引用都已被丢弃,以便该对象可以被回收)。
  • 老实说,我不喜欢在数据流管道中将 IDisposable 对象作为消息传递。通过使用using 语句或try/finally 块,您可以轻松地保证一次性对象被放置在创建它的方法中。但是如果让它逃到外面,处理它的责任就不再是局部的,确保它最终被处理就变得棘手了。我没有简单的解决方案来解决这个问题。
  • 这是个好建议,谢谢!

标签: c# memory-management task-parallel-library tpl-dataflow


【解决方案1】:

这是 TPL 的预期行为吗?

是的。它不会root所有对象(它们可用于垃圾收集和终结),但它也不会释放它们。

是否可以设置任何选项,以便在每次迭代结束时释放资源?

没有。

如何确保在输入上执行最后一个块/动作时自动调用 dispose?

要处理对象,您的代码应该调用Dispose。这很容易通过修改 ProcessImage 或将其包装在委托中来完成。

如果ProcessImage 是同步的:

var proImage = new TransformBlock<Image, double>(image => { using (image) return ProcessImage(image); });

或者如果它是异步的:

var proImage = new TransformBlock<Image, double>(async image => { using (image) return await ProcessImage(image); });

【讨论】:

  • 问题在于,如果proImage 块失败,存储在其输入缓冲区中的所有Image 对象都将被静默删除。这些对象不会被释放,并且它们持有的托管资源不会在垃圾收集器最终确定对象之前被释放(在未来的某个随机时刻)。
  • 好点。我的回答不能很好地处理错误情况。我想可以将它们添加到一次性集合中,但最终你会得到一个可能不断增长的列表......
  • 另一个想法是通过将消息包装在一些尚未发明的Try+Using monad 中来使块防弹,就像类固醇上的Try。 :-)
  • 我有点累 ATM - 我似乎无法弄清楚它是如何工作的。如果 using monad 输入消息被丢弃,它们仍然不会被急切地处理,对吧?
  • @TheodorZoulias:啊,当然。 Try monad 将完全防止故障块。
猜你喜欢
  • 2014-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多