TPL 数据流 BufferBlock<> 消费者在 WPF 的主线程中工作
TPL Dataflow BufferBlock<> consumer works in main-thread in WPF
我有一个简单的生产者-消费者 class 使用 BufferBlock
对象来 post 改编自 TPL Dataflow Docs 的消息,我的问题是在 WPF 应用程序中使用它导致消费者在同一个主线程中接收数据,而控制台应用程序在工作线程中执行它。这是我的代码:
public class DataflowProducerConsumer
{
BufferBlock<int> _buffer;
public DataflowProducerConsumer()
{
_buffer = new BufferBlock<int>();
var consumer = UseBufferBlock(_buffer);
}
public void Produce(int number)
{
_buffer.Post(number);
var actionBlock = UseActionBlock();
actionBlock.Post(number);
}
public async Task UseBufferBlock(ISourceBlock<int> source)
{
while (await source.OutputAvailableAsync())
{
int data = source.Receive();
// ...process data asyncron
Console.WriteLine("BufferBlock-Thread {0} received: {1}", Thread.CurrentThread.ManagedThreadId, data);
}
}
public ITargetBlock<int> UseActionBlock()
{
return new ActionBlock<int>(data =>
{
Console.WriteLine(string.Format("ActionBlock-Thread: {0} received: {1}", Thread.CurrentThread.ManagedThreadId, data));
});
}
}
在 main 或点击事件中执行的测试:
var obj = new DataflowProducerConsumer();
obj.Produce(19);
嗯,我把Post
改成了SendAsync
,没成功。 ActionBlock
部分效果很好。有什么不同?我做错了什么?
这与 TPL DataFlow 本身无关,而是与以下语句相关:
await source.OutputAvailableAsync()
await 将捕获当前的 SynchronizationContext
和 post 继续(await 之后的其余方法)。如果没有同步上下文(如在控制台应用程序中)-它将 post 继续到线程池线程。
WPF 具有同步上下文,并且 post 访问它是 post 访问 UI 线程(如 Dispatcher.BeginInvoke()
),这就是您所观察到的。
为了防止捕获当前同步上下文(因此 - 总是 post 继续线程池线程),使用 ConfigureAwait(false)
:
while (await source.OutputAvailableAsync().ConfigureAwait(false))
{
int data = source.Receive();
// ...process data asyncron
Console.WriteLine("BufferBlock-Thread {0} received: {1}", Thread.CurrentThread.ManagedThreadId, data);
}
另请注意,有 ReceiveAsync()
方法,您可以使用(并等待)代替同步 Receive()
,尽管这与所讨论的问题无关。
我有一个简单的生产者-消费者 class 使用 BufferBlock
对象来 post 改编自 TPL Dataflow Docs 的消息,我的问题是在 WPF 应用程序中使用它导致消费者在同一个主线程中接收数据,而控制台应用程序在工作线程中执行它。这是我的代码:
public class DataflowProducerConsumer
{
BufferBlock<int> _buffer;
public DataflowProducerConsumer()
{
_buffer = new BufferBlock<int>();
var consumer = UseBufferBlock(_buffer);
}
public void Produce(int number)
{
_buffer.Post(number);
var actionBlock = UseActionBlock();
actionBlock.Post(number);
}
public async Task UseBufferBlock(ISourceBlock<int> source)
{
while (await source.OutputAvailableAsync())
{
int data = source.Receive();
// ...process data asyncron
Console.WriteLine("BufferBlock-Thread {0} received: {1}", Thread.CurrentThread.ManagedThreadId, data);
}
}
public ITargetBlock<int> UseActionBlock()
{
return new ActionBlock<int>(data =>
{
Console.WriteLine(string.Format("ActionBlock-Thread: {0} received: {1}", Thread.CurrentThread.ManagedThreadId, data));
});
}
}
在 main 或点击事件中执行的测试:
var obj = new DataflowProducerConsumer();
obj.Produce(19);
嗯,我把Post
改成了SendAsync
,没成功。 ActionBlock
部分效果很好。有什么不同?我做错了什么?
这与 TPL DataFlow 本身无关,而是与以下语句相关:
await source.OutputAvailableAsync()
await 将捕获当前的 SynchronizationContext
和 post 继续(await 之后的其余方法)。如果没有同步上下文(如在控制台应用程序中)-它将 post 继续到线程池线程。
WPF 具有同步上下文,并且 post 访问它是 post 访问 UI 线程(如 Dispatcher.BeginInvoke()
),这就是您所观察到的。
为了防止捕获当前同步上下文(因此 - 总是 post 继续线程池线程),使用 ConfigureAwait(false)
:
while (await source.OutputAvailableAsync().ConfigureAwait(false))
{
int data = source.Receive();
// ...process data asyncron
Console.WriteLine("BufferBlock-Thread {0} received: {1}", Thread.CurrentThread.ManagedThreadId, data);
}
另请注意,有 ReceiveAsync()
方法,您可以使用(并等待)代替同步 Receive()
,尽管这与所讨论的问题无关。