C# TransformBlock项卡在输出队列中。为什么以及如何修复?
我已经浏览了TPL数据流,并使用链接到C# TransformBlock项卡在输出队列中。为什么以及如何修复?,c#,.net,async-await,task-parallel-library,tpl-dataflow,C#,.net,Async Await,Task Parallel Library,Tpl Dataflow,我已经浏览了TPL数据流,并使用链接到ActionBlock的trasforblock在代码中遇到了一个非常恼人的问题 最后,我发现items被困在TransformBlock的输出队列中,因为它的OutputCount属性连续返回大于“0”的值。 这就是整个应用程序死锁的原因。但是,只要我调用TransformBlock.TryReceiveAll(),它就会解除阻塞 任何人,请告诉我,如果有什么我错过了或如何防止这种行为 static void Main() { int total
ActionBlock
的trasforblock
在代码中遇到了一个非常恼人的问题
最后,我发现items被困在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();
}
}
static void Main()
{
int-total=0;
int itemsProcessing=0;
TransformBlock TransformBlock=新TransformBlock(
i=>新元组(i,Math.Sqrt(i)),
新的ExecutionDataflowBlockOptions
{
边界容量=20,
MaxDegreeOfParallelism=DataflowBlockOptions.Unbounded
});
ActionBlock outputBlock=新的ActionBlock(异步元组=>
{
等待任务。延迟(1000);//模拟数据输出延迟
联锁。减量(参考项目处理);
},
新的ExecutionDataflowBlockOptions
{
边界容量=5,
MaxDegreeOfParallelism=DataflowBlockOptions.Unbounded
});
transformBlock.Completion.ContinueWith(t=>outputBlock.Complete());
使用(计时器=新计时器)(o=>
{
Console.Title=string.Format(
"{0}: {1}/{2} {3}/{4}/{5}",
Assembly.GetExecutionGassembly().GetName().Name,
Volatile.Read(参考项处理),Volatile.Read(参考总数),
transformBlock.InputCount、transformBlock.OutputCount、outputBlock.InputCount);
},空,100100)
{
使用(transformBlock.LinkTo(outputBlock,新数据流链接选项{PropagateCompletion=true}))
{
对于(int i=0;i<40;i++)
{
Thread.Sleep(100);//模拟新项目检索延迟
联锁增量(参考总数);
联锁增量(参考项目处理);
transformBlock.SendAsync(i.Wait();
}
}
控制台写入线(“排队”);
transformBlock.Complete();
outputBlock.Completion.Wait();
控制台。写入线(“完成”);
timer.Change(Timeout.Infinite,Timeout.Infinite);
timer.Dispose();
}
}
调用TransformBlock.LinkTo
将返回一次性注册。处理该注册后,块将取消链接
您的使用
范围结束得太快,在转换块
之前,这些块取消链接,有机会将自身清空到操作块
中,从而使其无法完成。由于第一个区块没有完成,下一个区块甚至没有开始完成,更不用说完成了
使用块移动中的等待可以解决死锁:
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");
}
使用(transformBlock.LinkTo(outputBlock,新的DataflowLinkOptions{PropagateCompletion=true}))
{
对于(int i=0;i<40;i++)
{
Thread.Sleep(100);//模拟新项目检索延迟
联锁增量(参考总数);
联锁增量(参考项目处理);
transformBlock.SendAsync(i.Wait();
}
控制台写入线(“排队”);
transformBlock.Complete();
outputBlock.Completion.Wait();
控制台。写入线(“完成”);
}
作为补充说明,您真的不应该以这种方式阻塞异步代码。使用async Wait代替Wait()
,Task.Delay
代替Thread.Sleep
等会简单得多
另外,由于您使用的是PropagateCompletion
,因此不需要显式调用outputBlock.Complete()
。i3arnon,谢谢,非常有用。至于旁注,这只是一个测试应用程序,当然在现实世界中,我会尝试使用async/await,而不是简单的阻塞。再次感谢。