TPL数据流块

时间:2014-08-21 17:58:59

标签: c# .net task-parallel-library async-await tpl-dataflow

问题:为什么使用WriteOnceBlock(或BufferBlock)从另一个BufferBlock<Action>获取答案(如回调)(回复答案发生在Action Actor 1}})导致死锁(在此代码中)?

我认为类中的方法可以被视为我们发送给对象的消息(就像我认为--Alan Kay所提出的关于OOP的原始观点)。所以我编写了这个通用的Actor类,它有助于将普通对象转换为public class Actor<T> { private readonly T _processor; private readonly BufferBlock<Action<T>> _messageBox = new BufferBlock<Action<T>>(); public Actor(T processor) { _processor = processor; Run(); } public event Action<T> Send { add { _messageBox.Post(value); } remove { } } private async void Run() { while (true) { var action = await _messageBox.ReceiveAsync(); action(_processor); } } } public interface IIdGenerator { long Next(); } (当然,由于可变性和事物,这里有许多看不见的漏洞,但这不是主要的关注点)

所以我们有这些定义:

static void Main(string[] args)
{
    var idGenerator1 = new IdInt64();

    var idServer1 = new Actor<IIdGenerator>(idGenerator1);

    const int n = 1000;
    for (var i = 0; i < n; i++)
    {
        var t = new Task(() =>
        {
            var answer = new WriteOnceBlock<long>(null);

            Action<IIdGenerator> action = x =>
            {
                var buffer = x.Next();

                answer.Post(buffer);
            };

            idServer1.Send += action;

            Trace.WriteLine(answer.Receive());
        }, TaskCreationOptions.LongRunning); // Runs on a separate new thread
        t.Start();
    }

    Console.WriteLine("press any key you like! :)");
    Console.ReadKey();

    Trace.Flush();
}

现在;为什么这段代码有效:

static void Main(string[] args)
{
    var idGenerator1 = new IdInt64();

    var idServer1 = new Actor<IIdGenerator>(idGenerator1);

    const int n = 1000;
    for (var i = 0; i < n; i++)
    {
        var t = new Task(() =>
        {
            var answer = new WriteOnceBlock<long>(null);

            Action<IIdGenerator> action = x =>
            {
                var buffer = x.Next();

                answer.Post(buffer);
            };

            idServer1.Send += action;

            Trace.WriteLine(answer.Receive());
        }, TaskCreationOptions.PreferFairness); // Runs and is managed by Task Scheduler 
        t.Start();
    }

    Console.WriteLine("press any key you like! :)");
    Console.ReadKey();

    Trace.Flush();
}

此代码不起作用:

TaskCreationOptions

此处使用不同的Task来创建[ThreadStatic]。也许我在这里对TPL Dataflow概念错了,刚开始使用它(A {{1}}隐藏在哪里?)。

1 个答案:

答案 0 :(得分:3)

您的代码存在的问题是这部分:answer.Receive()。 当你在动作中移动它时,僵局不会发生:

var t = new Task(() =>
{
    var answer = new WriteOnceBlock<long>(null);
    Action<IIdGenerator> action = x =>
    {
        var buffer = x.Next();
        answer.Post(buffer);
        Trace.WriteLine(answer.Receive());
    };
    idServer1.Send += action;
});
t.Start();

那为什么呢? answer.Receive();,而不是await answer.ReceiveAsnyc();阻止线程,直到返回答案。当你使用TaskCreationOptions.LongRunning时,每个任务都有自己的线程,所以没有问题,但没有它(TaskCreationOptions.PreferFairness无关紧要)所有线程池线程都忙着等待所以一切都很多慢点。它实际上并没有死锁,你可以看到当你使用15而不是1000时。

还有其他解决方案可以帮助您理解问题:

  • 在原始代码之前使用ThreadPool.SetMinThreads(1000, 0);增加线程池。
  • 使用ReceiveAsnyc

Task.Run(async () =>
{
    var answer = new WriteOnceBlock<long>(null);
    Action<IIdGenerator> action = x =>
    {
        var buffer = x.Next();
        answer.Post(buffer);
    };
    idServer1.Send += action;          
    Trace.WriteLine(await answer.ReceiveAsync());
});