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)需要调用code>,然后调用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());
});