繁体   English   中英

TPL数据流块

[英]TPL Dataflow Blocks

问题:为什么使用WriteOnceBlock (或BufferBlock )从另一个BufferBlock<Action>取回答案(如某种回调)(获取答案在发布的Action发生),会导致死锁(在此代码中)?

我认为类中的方法可以视为我们正在发送给对象的消息(例如,我认为-Alan Kay提出的有关OOP的原始观点)。 因此,我编写了这个通用的Actor类,该类有助于将普通对象转换为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();
}

此处用于创建Task的不同TaskCreationOptions 也许我对这里的TPL Dataflow概念是错误的,才刚刚开始使用它(一个[ThreadStatic]隐藏在某个地方?)。

您的代码有问题的部分是: 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);增加线程池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());
});

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM