簡體   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