2010-05-13 42 views
4

我有一些使用异步编程模型(APM)习惯用语(例如BeginRead/EndRead)在C#中编写的高性能文件传输代码。该代码从本地磁盘读取文件并将其写入套接字。使用任务并行库执行异步I/O的建议

为了在现代硬件上获得最佳性能,尽可能在飞行中保留多个未完成的I/O操作非常重要。因此,我在该文件上发布了几个BeginRead操作,然后当完成时,我在套接字上调用BeginSend,完成后我在文件上执行另一个BeginRead。细节比这个更复杂一些,但是在这个想法的高层次上。

我已经有了基于APM的代码,但它很难遵循,并且可能有微妙的并发错误。我很乐意使用TPL代替它。我想Task.Factory.FromAsync只是要做,但有一个问题。

我见过的所有I/O示例(最特别是并行扩展附件中的StreamExtensions类)假定一次读取后再写入一次。这不会以我需要的方式执行。

我不能用一些简单的像Parallel.ForEach或其它功能扩展Task.Factory.Iterate因为异步I/O任务不会花很多时间在一个工作线程,因此并行刚刚启动另一个任务,从而可能导致数十或数百待处理的I/O操作;太多了!你可以在你的任务上解决这个问题,但是这会导致创建一个事件句柄(一个内核对象),并阻塞等待一个任务等待句柄,该句柄绑定一个工作线程。我的基于APM的实现避免了这两件事。

我一直在玩各种不同的方式来保持多个读/写操作在飞行中,并且我设法使用延续来调用创建另一个任务的方法,但它感觉尴尬,而且肯定不会感觉不像惯用的TPL。

有没有其他人正在用TPL解决这个问题?有什么建议么?

回答

2

如果您担心线程过多,则可以在致电Parallel.ForEach时将ParallelOptions.MaxDegreeOfParallelism设置为可接受的数字。

+0

感谢您的回复。这是真的,我可以做到这一点,但'Parallel.ForEach'方式在运行任务中实现了'Wait'。我曾希望避免这种情况,因为: 'Wait'使用'ManualResetEventSlim',它尝试一个自旋锁,然后恢复为一个内核的Event对象。 创建内核事件对象需要很多指令并切换到内核模式。 等待内核事件对象需要切换到内核模式。 由于我不需要使用APM实现这些功能,所以我希望有一些习惯性的TPL方式可以避免它们。 – anelson 2010-05-13 15:09:53

+0

执行I/O时,最后需要担心的是“许多指令和切换到内核模式”。但是由于'EndRead'也在等待事件句柄,所以我不理解你的担心。 – Gabe 2010-05-13 15:59:31

+0

我在自己的测试工具中尝试了这一点,我很惊讶,做这些等待的惩罚是多么微不足道。 这不是我想要的答案,但它似乎是正确的,所以我接受了这个答案。 – anelson 2010-05-13 20:19:23