会写这篇纯属机缘巧合,虽然一直以来认为对单一文件的读、写操作是不冲突,可并行的,但实际并未实践过。正好有个UWP的程序要并行读取由Desktop Extension创建的文本,需要有个原型程序来验证,那不妨点开最新的VS 2022,顺手试试新的语法糖。
首先我们明确本篇对文件的操作均通过FileStream类来实现,FileStream在.NET 6进行了完全的重写,提高了性能和可靠性。但是本篇提到的共享读写权限,在之前版本也是完全支持的。
本篇提到的同时读写功能依赖FileStream的这个构造函数:
public FileStream (string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share);
接下来我们通过实际的代码来进行分析。创建第一个工程CreateWriteSharedFile,该工程为.NET 6的Console程序,用于新建和写入内容到名为TestFile.txt的文件中。
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "TestFile.txt"); var fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite); StreamWriter sw = new StreamWriter(fileStream); int cout = 0; while (true) { for (var i = 0; i < 10; i++) { sw.WriteLine(cout++); Console.WriteLine(cout); } sw.Flush(); await Task.Delay(1000); }
没有命名空间,没有类名和Main函数,这是C# 10里的新语法糖——顶级语句。作为简化后的程序入口点,十分适合简短的示例程序,对初学者也更友好。
代码的内容也很好懂,就是每隔1秒连续写入10个自增的数字。唯一值得留意的是FileShare.ReadWrite,这个枚举标识对应的是后续其他对该文件的请求,不管是该进程内还是另外进程,均给与ReadWrite的权限。
我们的第二个工程ReadSharedFile仅做读取的操作,所以上面CreateWriteSharedFile中的FileShare只给Read也可以。但是相反,ReadSharedFile因为要允许CreateWriteSharedFile来进行写操作,所以它必须给与FileShare.Write枚举。
var path =Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "TestFile.txt"); var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read,FileShare.Write); var reader = new StreamReader(fileStream); while (!reader.EndOfStream) { Console.WriteLine(reader.ReadLine()); await Task.Delay(1000); }
上述代码是在ReadSharedFile工程中读取由CreateWirteSharedFile创建的TestFile.txt中的内容。想要测试的话,build成功后运行对应的exe文件即可。并行的读和写操作较为容易理解,也不会存在冲突或生成脏数据的问题。
但如果是同时进行写操作会怎么样呢?之前的FileShare.ReadWrite就是为接下来的测试准备的。我们创建第二个写文件的工程SecondWriteSharedFile,同样要注意除了设置Read以外,还要为CreateWriteSharedFile特别准备Write权限,才能实现两边同时写入该文件的要求。
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "TestFile.txt"); var fileStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite); StreamWriter sw = new StreamWriter(fileStream); while (true) { for (var i = 0; i < 10; i++) { sw.WriteLine("A".PadRight(i,'A')); Console.WriteLine("A".PadRight(i, 'A')); } sw.Flush(); await Task.Delay(1000); }
非常不幸的是,SecondWriteSharedFile在默认情况下,同样会从文件的头部开始写入,这样就覆盖了先运行的CreateWriteSharedFile在同样位置写入的内容。所以在一般情况下,我们要避免并行的写操作,这样极容易互相覆盖产生脏数据。
本篇简单地讨论了通过FileShare枚举,使用FileStream并行地读写文件的一般场景。希望能够抛砖引玉,给各位大佬在实际生产场景中以微小的帮助。
示例代码:(因为GitHub经常打不开,我在gitee也同样放了一份)
https://github.com/manupstairs/FileReadWriteSample
https://gitee.com/manupstairs/FileReadWriteSample
以下链接,是MS Learn上Windows开发的入门课程,单个课程三十分钟到60分钟不等,想要补充基础知识的同学点这里:
开始使用 Visual Studio 开发 Windows 10 应用
开发 Windows 10 应用程序
编写首个 Windows 10 应用
创建 Windows 10 应用的用户界面 (UI)
增强 Windows 10 应用的用户界面
在 Windows 10 应用中实现数据绑定