我需要进行可扩展的流程。该进程主要具有I / O操作,并且具有一些次要的CPU操作(主要是反序列化字符串)。该过程在数据库中查询URL列表,然后从这些URL中获取数据,将下载的数据反序列化为对象,然后将其中的某些数据持久化为crm动态以及另一个数据库。之后,我需要更新第一个处理了URL的数据库。部分要求是使并行度可配置。
最初,我想通过一系列等待任务来实现它,并使用信号量来限制并行度-非常简单。然后,我在这里阅读了@Stephen Cleary的一些文章和答案,这些文章和答案建议使用TPL Dataflow,我认为这可能是一个不错的选择。但是,我想确保通过使用Dataflow来使代码“复杂化”,这是值得的。我还建议使用一个ForEachAsync extension method,它也很容易使用,但是由于它对集合进行分区的方式,我不确定它是否不会引起内存开销。
在这种情况下,TPL Dataflow是一个不错的选择吗?它比Semaphore或ForEachAsync方法更好吗-如果通过TPL DataFlow实现它比其他每个选项(Semaphore / ForEachASync),我实际上将获得什么好处?
答案 0 :(得分:6)
该进程主要具有IO操作以及一些次要的CPU操作(主要是反序列化字符串)。
几乎就是I / O。除非这些字符串巨大,否则反序列化将不值得并行化。您正在做的CPU工作类型会因为噪音而消失。
因此,您将专注于并发异步。
SemaphoreSlim
是实现此目的的标准模式。 ForEachAsync
可以采用多种形式;请注意,在您引用的blog post中,此方法有 5 个不同的实现,每个实现都是有效的。 “这里有许多可能用于迭代的语义,每种语义都会导致不同的设计选择和实现。”出于您的目的(不希望CPU并行化),您不应该考虑使用Task.Run
或分区。在异步并发世界中,任何ForEachAsync
实现都只是语法糖,它隐藏了实现的语义,这就是为什么我倾向于避免使用它。
这给您留下SemaphoreSlim
与ActionBlock
的对比。我通常建议人们首先从SemaphoreSlim
开始,如果他们的需求变得更加复杂(以某种方式,他们似乎将从数据流管道中受益),考虑考虑使用TPL数据流。
例如,“部分要求是使并行度可配置。”
您可以从允许一定程度的并发开始-被限制的事情是一个整体操作(从url获取数据,将下载的数据反序列化为对象,持久化为crm动态和另一个数据库,然后更新第一个数据库)。这是SemaphoreSlim
将是完美解决方案的地方。
但是您可能会决定要使用多个旋钮:例如,对于要下载的URL数量,一个并发程度,对于持久化的单独并发程度,以及对于更新原始数据库的单独的并发程度。然后,您还需要限制这些点之间的“队列”:仅在内存中存储如此多的反序列化对象等,以确保使用慢速数据库的快速url不会导致应用程序使用过多的问题记忆。如果这些是有用的语义,那么您已经开始从数据流的角度解决问题,这就是使用TPL Dataflow之类的库可能会更好的服务点。
答案 1 :(得分:0)
以下是信号量方法的卖点:
以下是 TPL Dataflow 方法的卖点:
例如,让我们回顾以下信号量实现:
string[] urls = FetchUrlsFromDB();
var cts = new CancellationTokenSource();
var semaphore = new SemaphoreSlim(10); // Degree of parallelism (DOP)
Task[] tasks = urls.Select(url => Task.Run(async () =>
{
await semaphore.WaitAsync(cts.Token);
try
{
string rawData = DownloadData(url);
var data = Deserialize(rawData);
PersistToCRM(data);
MarkAsCompleted(url);
}
finally
{
semaphore.Release();
}
})).ToArray();
Task.WaitAll(tasks);
以上实现可确保在任何给定时刻最多同时处理10个url。但是,这些并行工作流之间将没有协调。因此,例如,在给定的时刻,所有10个并行工作流都将下载数据,在另一时刻,所有10个将对原始数据进行反序列化,而在另一时刻,所有10个将数据持久化到CRM,则完全有可能。这远非理想。 理想情况下,您希望使整个操作(无论是网络适配器,CPU还是数据库服务器)的瓶颈始终不间断地工作,并且在各种随机时刻都不会被利用不足(或完全闲置)。 / p>
另一个要考虑的问题是,对于每个异构操作,并行化的最佳程度是多少。 10 DOP对于与Web的通信可能是最佳的,但是对于与数据库的通信来说太低或太高。信号量方法不允许进行这种级别的微调。您唯一的选择是通过在这些最佳值之间的某个位置选择DOP值来妥协。
如果URL的数量很大,可以说是1,000,000,那么上面的Semaphore方法也会引起严重的内存使用注意事项。网址的平均大小可能为50个字节,而连接到Task
的{{1}}可能重10倍甚至更多。当然,您可以更改实现并以更聪明的方式使用SemaphoreSlim
,而不会产生太多任务,但这会违背该方法的主要(唯一)卖点,即简单性。 / p>
TPL Dataflow库解决了所有这些问题,但付出的代价是(小的)学习曲线,以便能够驯服这个强大的工具。