FileStream的同步需求(Begin/End)(读/写)

9

以下多线程调用模式对于 .Net FileStream 是可接受的吗?

多个线程调用如下方法:

ulong offset = whatever; // different for each thread
byte[] buffer = new byte[8192];
object state = someState; // unique for each call, hence also for each thread

lock(theFile)
{
    theFile.Seek(whatever, SeekOrigin.Begin);
    IAsyncResult result = theFile.BeginRead(buffer, 0, 8192, AcceptResults, state);
}

if(result.CompletedSynchronously)
{
    // is it required for us to call AcceptResults ourselves in this case?
    // or did BeginRead already call it for us, on this thread or another?
}

其中AcceptResults是:

void AcceptResults(IAsyncResult result)
{
    lock(theFile)
    {
         int bytesRead = theFile.EndRead(result);

         // if we guarantee that the offset of the original call was at least 8192 bytes from
         // the end of the file, and thus all 8192 bytes exist, can the FileStream read still
         // actually read fewer bytes than that?

         // either:
         if(bytesRead != 8192)
         {
             Panic("Page read borked");
         }

         // or:
         // issue a new call to begin read, moving the offsets into the FileStream and
         // the buffer, and decreasing the requested size of the read to whatever remains of the buffer
    }
}

我感到困惑,因为文档对我来说不太清楚。例如,FileStream类如下所示:
任何此类型的公共静态成员都是线程安全的。任何实例成员不能保证是线程安全的。
但是BeginRead的文档似乎考虑了有多个读取请求正在进行:
多个同时异步请求会导致请求完成顺序不确定。
多个读取请求是否允许同时进行?写入呢?这是否是在调用Seek和BeginRead之间保护流的Position位置的适当方式?还是需要一直保持该锁定直到EndRead,因此一次只能有一个读取或写入请求?
我理解回调将在不同的线程上发生,并且我的处理state、buffer的方式可以允许多个读取请求同时进行。
此外,有人知道在文档中哪里可以找到这些问题的答案吗?或者由内行人撰写的文章?我一直在搜索,但找不到任何信息。
相关文档:

FileStream类

Seek方法

BeginRead方法

EndRead方法

IAsyncResult接口

带有一些新信息的编辑

使用Reflector进行快速检查显示,BeginRead确实将流位置捕获到每个调用状态中(NativeOverlapped结构的某些字段)。看起来EndRead没有以任何明显的方式查询流位置。这显然并不是最终结论,因为它可能以非明显的方式存在,或者在底层本地API中不受支持。

1个回答

1

是的,文档很简略。不幸的是,没有更好的文档。

编辑:实际上,乔·达菲(Joe Duffy)的书《Windows并发编程》第8章APM解释了异步API、IAsyncResult等(好书和作者)。但基本问题在于MSDN说实例变量不是线程安全的,因此需要适当的同步。

所以你有多个线程在同一个theFile实例上启动BeginRead?BeginRead页面确实提到了这一点:“必须为每次调用BeginRead精确地调用EndRead。在开始另一个读取之前未结束读取过程可能会导致不良行为,如死锁。”同时,您正在调用theFile对象上的Seek,而其他线程可能正在执行其BeginRead回调的中间。完全不安全。


1
为什么一个线程正在 Seek,而另一个线程正在执行其回调会有影响呢?假设回调意味着请求的读取已完成,对吧?我更担心在 BeginRead 和匹配的回调之间调用 Seek 的时间。除非我漏掉了什么,上面的代码对于每次对 BeginRead 的调用都恰好调用一次 EndRead,除了一些不确定性,即当 BeginRead 调用其回调时,它返回的 IAsyncResult 是否 CompletedSynchronously。 - Doug McClean
是的,每个BeginRead都有一个EndRead。但是,不能保证在另一个线程启动其BeginRead之前会调用EndRead,锁无法保护该场景。 - Chris O
哦,你在这里可能没问题,BeginRead页面确实说“在Windows上,所有小于64 KB的I/O操作都将同步完成以获得更好的性能。对于小于64 KB的缓冲区大小,异步I/O可能会影响性能。”所以你确实保证了所有读取都通过锁进行序列化。但是如果所有线程都通过锁和同步读取进行序列化,为什么还要使用异步API呢? - Chris O
1
好的,我漏掉了关于缓冲区大小的部分。是否需要保护一种情况,即在另一个线程开始其BeginRead之前,EndRead没有被调用?这是我的问题。文档对我来说暗示着不需要,否则为什么会说“多个同时的异步请求使得请求完成顺序不确定”,而不是“多个同时的异步请求不受支持/导致未定义行为/会让恶魔从你的鼻子里飞出来”呢? - Doug McClean
2
解读MSDN让我的鼻子里飞出了恶魔;-)那个特定页面明显自相矛盾(MSDN文档错误并不罕见),尽管我认为死锁声明是正确的,而多个同时异步请求是错误的,但这只是一种假设。 - Chris O

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接