我已经浏览了TPL DataFlow,并且在使用与TrasformBlock
关联的ActionBlock
的代码中遇到了一个非常恼人的问题。
最终我发现项目卡在TransformBlock
的输出队列中,因为它的OutputCount
属性连续返回高于“0”的值。
这就是为什么整个应用程序陷入僵局。但是,只要我拨打TransformBlock.TryReceiveAll()
,就会取消阻止。
任何人,请告诉我,如果我错过了什么或如何防止这种行为?
static void Main()
{
int total = 0;
int itemsProcessing = 0;
TransformBlock<int, Tuple<int, double>> transformBlock = new TransformBlock<int, Tuple<int, double>>(
i => new Tuple<int, double>(i, Math.Sqrt(i)),
new ExecutionDataflowBlockOptions
{
BoundedCapacity = 20,
MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded
});
ActionBlock<Tuple<int, double>> outputBlock = new ActionBlock<Tuple<int, double>>(async tuple =>
{
await Task.Delay(1000); // simulating data output delay
Interlocked.Decrement(ref itemsProcessing);
},
new ExecutionDataflowBlockOptions
{
BoundedCapacity = 5,
MaxDegreeOfParallelism = DataflowBlockOptions.Unbounded
});
transformBlock.Completion.ContinueWith(t => outputBlock.Complete());
using (Timer timer = new Timer(o =>
{
Console.Title = string.Format(
"{0}: {1}/{2} {3}/{4}/{5}",
Assembly.GetExecutingAssembly().GetName().Name,
Volatile.Read(ref itemsProcessing), Volatile.Read(ref total),
transformBlock.InputCount, transformBlock.OutputCount, outputBlock.InputCount);
}, null, 100, 100))
{
using (transformBlock.LinkTo(outputBlock, new DataflowLinkOptions { PropagateCompletion = true }))
{
for (int i = 0; i < 40; i++)
{
Thread.Sleep(100); // simulating new item retrieval delay
Interlocked.Increment(ref total);
Interlocked.Increment(ref itemsProcessing);
transformBlock.SendAsync(i).Wait();
}
}
Console.WriteLine("Enqueued");
transformBlock.Complete();
outputBlock.Completion.Wait();
Console.WriteLine("Finish");
timer.Change(Timeout.Infinite, Timeout.Infinite);
timer.Dispose();
}
}
答案 0 :(得分:1)
调用TransformBlock.LinkTo
会让您退回一次性注册。当您处置该注册时,块将取消链接。
using
范围过早结束,并且在TransformBlock
有机会将自身清空到ActionBlock
以阻止其无法完成之前,块会取消链接。由于第一个区块没有完成,下一个区块甚至没有开始完成,更不用说完成了。
在using
块内移动等待解决了死锁问题:
using (transformBlock.LinkTo(outputBlock, new DataflowLinkOptions { PropagateCompletion = true }))
{
for (int i = 0; i < 40; i++)
{
Thread.Sleep(100); // simulating new item retrieval delay
Interlocked.Increment(ref total);
Interlocked.Increment(ref itemsProcessing);
transformBlock.SendAsync(i).Wait();
}
Console.WriteLine("Enqueued");
transformBlock.Complete();
outputBlock.Completion.Wait();
Console.WriteLine("Finish");
}
作为旁注,你真的不应该以这种方式阻止异步代码。使用async-await而不是Wait()
,Task.Delay
而不是Thread.Sleep
等会更简单。
另外,由于您使用的是PropagateCompletion
,因此无需明确调用outputBlock.Complete()
。