以下是一些代码,它通过定期在后台线程上打印写入流的位置来监视磁盘的写入进度,同时将10 GB的数据写入磁盘:

string path = "test.out";
long size = 10 * 1000L * 1000L * 1000L;
using (FileStream writer = new FileStream(path, FileMode.Create, FileAccess.Write))
{
    // Get a handle (and don't do anything with it)
    var handle = writer.SafeFileHandle;

    // Start a background position reader
    ThreadPool.QueueUserWorkItem(s =>
    {
        while (true)
        {
            Console.WriteLine(writer.Position);
            Thread.Sleep(10);
        }
    });

    // Write out the bits
    byte[] buffer = new byte[4096];
    long position = 0;
    while (position < size)
    {
        int count = (int)Math.Min(size - position, buffer.Length);
        writer.Write(buffer, 0, count);
        position += count;
    }

    Console.ReadLine();
}


如果运行此代码,将看到少于10 GB的内容被写入。基本上,随机的一小部分写入操作会被忘记,并且不会进入磁盘。

这个问题很少发生。此代码尝试写入的10 GB中,有99%以上成功写入。如果您较少阅读“位置”,则问题发生的频率会更少。我们发现此问题的原因是,我们有一些代码可以监视机器对机器文件副本的吞吐量(通过在后台线程上每30秒读取一次位置),并且在数十亿个文件中检测到数百个文件损坏的实例。我们每天制作的副本。但是,从另一个线程监视流进度的基本方案似乎非常普遍,因此,尽管命中率很低,但这可能会打击很多人。

效果不取决于是否使用旧的线程池API或新的基于任务的API,是否使用Write或WriteAsync或对Dispose / Close的谨慎程度。这确实取决于是否公开了文件句柄:如果注释掉读取SafeFileHandle属性的行,则会写入所有10 GB的内容。请注意,我们实际上对手柄不做任何事情;仅仅阅读它会导致不当行为。

最佳答案

这里发生的是FileStream(https://referencesource.microsoft.com/#mscorlib/system/io/filestream.cs,e23a38af5d11ddd3)维护一个布尔标志_exposedHandle,如果它认为内部使用的句柄已在外部公开,则为true。如果_exposedHandle为true,则当您读取Position时,它将运行一个私有方法VerifyOSHandlePosition(),该方法将自己的内部位置值与该句柄的内部值进行同步,然后返回它。由于该同步代码不是线程安全的,因此同时进行的读写操作可能会更加麻烦。

现在FileStream不再声称是线程安全的。但这是一种弱势防御,因为每个人都希望FileStream对于状态更改的读取和写入当然并不安全,但是纯属性读取仍然应该没有副作用,因此本质上是线程安全的。例如,List和Dictionary不是线程安全的,但是读取它们的Count属性不会破坏发生在另一个线程上的读写。

我可以猜测为什么FileStream的作者添加了此内容。它允许您持有一个外部句柄,并使用FileStream和该句柄进行(同步)读写。但是我认为这不是正确的方法。如果您拥有另一个类也正在内部使用的外部资源(例如,您所传递给它的类或方法也使用的数组),那么就不要搞砸了。该类不应尝试以改变类功能的方式进行补偿(并使所有操作也受到性能影响),而应制作一个公共的SynchronizeHandlePosition()方法,并告诉想要此方案的人用它。

由于FileStream是它的本质,因此请记住:


尽可能避免使用带有暴露句柄的FileStreams。
知道带有暴露句柄的FileStream的Position具有线程不安全的副作用。
知道具有公开句柄的FileStream会降低性能。


如果Microsoft更新文档说出这些内容,那将是很好的。

关于c# - FileStream.Position具有线程不安全的副作用,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56085208/

10-17 02:42