C# TPL数据流块

C# TPL数据流块,c#,.net,task-parallel-library,async-await,tpl-dataflow,C#,.net,Task Parallel Library,Async Await,Tpl Dataflow,问题:为什么使用WriteOnceBlock(或BufferBlock)从另一个BufferBlock(返回答案发生在发布的操作中)获取答案(类似于回调)会导致死锁(在此代码中) 我认为类中的方法可以被视为发送给对象的消息(就像Alan Kay提出的关于OOP的原始观点)。因此,我编写了这个通用的Actor类,它帮助将普通对象转换为Actor(当然,由于可变性和其他原因,这里有很多看不见的漏洞,但这不是这里主要关注的问题) 所以我们有这些定义: public class Actor<T&g

问题:为什么使用
WriteOnceBlock
(或
BufferBlock
)从另一个
BufferBlock
(返回答案发生在发布的
操作中)获取答案(类似于回调)会导致死锁(在此代码中)

我认为类中的方法可以被视为发送给对象的消息(就像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();
}
公共类参与者
{
专用只读T_处理器;
私有只读缓冲块_messageBox=new BufferBlock();
公共参与者(T处理器)
{
_处理器=处理器;
Run();
}
公共活动行动发送
{
添加{u messageBox.Post(value);}
删除{}
}
私有异步无效运行()
{
while(true)
{
var action=wait_messageBox.ReceiveAsync();
动作(处理器);
}
}
}
公共接口IIdGenerator
{
长下一步();
}
现在;此代码的工作原理:

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(字符串[]args)
{
var idGenerator1=new idit64();
var idServer1=新参与者(idGenerator1);
常数int n=1000;
对于(变量i=0;i
{
var answer=new WriteOnceBlock(null);
动作=x=>
{
var buffer=x.Next();
答:邮政(缓冲);
};
idServer1.Send+=操作;
Trace.WriteLine(answer.Receive());
},TaskCreationOptions.LongRunning);//在单独的新线程上运行
t、 Start();
}
WriteLine(“按任意键!”:);
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();
}
static void Main(字符串[]args)
{
var idGenerator1=new idit64();
var idServer1=新参与者(idGenerator1);
常数int n=1000;
对于(变量i=0;i
{
var answer=new WriteOnceBlock(null);
动作=x=>
{
var buffer=x.Next();
答:邮政(缓冲);
};
idServer1.Send+=操作;
Trace.WriteLine(answer.Receive());
},TaskCreationOptions.PreferFairity);//运行并由任务计划程序管理
t、 Start();
}
WriteLine(“按任意键!”:);
Console.ReadKey();
Trace.Flush();
}

不同的
任务创建选项
用于创建
任务
s。也许我在这里对TPL数据流概念的理解是错误的,我刚刚开始使用它(一个
[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();

你在哪里被封锁?WaitForExit背后是什么?我用它的主体替换了
WaitForExit
;它基本上只是对
控制台的一个调用。ReadKey()
。我希望
运行()
Actor
类,异步地一个接一个地执行发布到
\u messageBox
操作;正确的?我不明白的是为什么在第二个代码中(其中使用
TaskCreationOptions.preferfairity
选项创建
Task
s)
Run()
不处理
\u messageBox
中的值(这是一个
BufferBlock
)?我必须补充一点,这段代码有很多问题。使用
async void
,事件订阅而不是方法调用,
new Task
而不是
Task.Run
我不明白你为什么要使用
WriteOnceBlock
WriteOnceBlock
被用作一个等待答案的容器;因此,不需要一些
ManualResetEvent
之类的东西。你关于代码的观点是正确的。但这里的问题不是由它们引起的;我不知道为什么会发生这种情况。只是附带说明:
ThreadPool.SetMinThreads(1000,0)不会做您期望的事情。第一个
ThreadPool.SetMaxThreads(1000,0),然后调用
ThreadPool.SetMinThreads(1000,0)将生效。感谢您的回复;顺便说一句,有一种实现等待应答容器的模式吗?@KavehShahbazian我不知道,但是你仍然可以使用
WriteOnceBlock
,只要确保你在异步等待并且没有阻塞线程。谢谢,但是我关于“异步”和“并行”的错误让我想起了关于“值类型”和我过去的“参考类型”。这一个需要更多的意识。:)
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());
});