如何锁定文件并在写入时避免读数

我的Web应用程序从文件系统返回一个文件。 这些文件是动态的,所以我无法知道它们中有多少个。 当该文件不存在时,应用程序将从数据库创建该文件。 我想避免两个不同的线程同时重新创建相同的文件,或者一个线程尝试返回该文件,而其他线程正在创建它。

此外,我不想锁定所有文件常见的元素。 因此,我应该在创建文件时锁定文件。

所以我想锁定一个文件,直到它的重新创建完成,如果其他线程试图访问它…它将不得不等待文件被解锁。

我一直在阅读关于FileStream.Lock,但我必须知道文件长度,它不会阻止其他线程尝试读取文件,因此它不适用于我的特定情况。

我一直在阅读有关FileShare.None的内容,但是如果其他线程/进程尝试访问该文件,它会抛出一个exception(哪个exception类型?)所以我应该开发一个“在故障时再试一次”,因为我我想避免exception生成……我不太喜欢这种方法,尽管可能没有更好的方法。

使用FileShare.None的方法或多或少会:

static void Main(string[] args) { new Thread(new ThreadStart(WriteFile)).Start(); Thread.Sleep(1000); new Thread(new ThreadStart(ReadFile)).Start(); Console.ReadKey(true); } static void WriteFile() { using (FileStream fs = new FileStream("lala.txt", FileMode.Create, FileAccess.Write, FileShare.None)) using (StreamWriter sw = new StreamWriter(fs)) { Thread.Sleep(3000); sw.WriteLine("trolololoooooooooo lolololo"); } } static void ReadFile() { Boolean readed = false; Int32 maxTries = 5; while (!readed && maxTries > 0) { try { Console.WriteLine("Reading..."); using (FileStream fs = new FileStream("lala.txt", FileMode.Open, FileAccess.Read, FileShare.Read)) using (StreamReader sr = new StreamReader(fs)) { while (!sr.EndOfStream) Console.WriteLine(sr.ReadToEnd()); } readed = true; Console.WriteLine("Readed"); } catch (IOException) { Console.WriteLine("Fail: " + maxTries.ToString()); maxTries--; Thread.Sleep(1000); } } } 

但我不喜欢这样的事实:我必须捕捉exception,尝试几次并等待不准确的时间:|

您可以通过使用流构造函数的FileMode.CreateNew参数来处理此问题。 其中一个线程将丢失,并发现该文件已经由另一个线程提前创建了一个微秒。 并将获得IOException。

然后它需要旋转,等待文件完全创建。 您使用FileShare.None强制执行此操作。 在这里捕捉exception并不重要,无论如何它都在旋转。 除非你P / Invoke,否则无论如何都没有其他的解决方法。

我认为正确的方法如下:创建一组字符串你将保存当前文件名,这样一个线程就会处理文件,就像这样

 //somewhere on your code or put on a singleton static System.Collections.Generic.HashSet filesAlreadyProcessed= new System.Collections.Generic.HashSet(); //thread main method code bool filealreadyprocessed = false lock(filesAlreadyProcessed){ if(set.Contains(filename)){ filealreadyprocessed= true; } else{ set.Add(filename) } } if(!filealreadyprocessed){ //ProcessFile } 

您是否有办法识别正在创建的文件?

假设这些文件中的每一个都对应于数据库中的唯一ID。 您创建了一个集中位置(Singleton?),其中这些ID可以与可锁定的内容(Dictionary)相关联。 需要读/写其中一个文件的线程执行以下操作:

 //Request access ReaderWriterLockSlim fileLock = null; bool needCreate = false; lock(Coordination.Instance) { if(Coordination.Instance.ContainsKey(theId)) { fileLock = Coordination.Instance[theId]; } else if(!fileExists(theId)) //check if the file exists at this moment { Coordination.Instance[theId] = fileLock = new ReaderWriterLockSlim(); fileLock.EnterWriteLock(); //give no other thread the chance to get into write mode needCreate = true; } else { //The file exists, and whoever created it, is done with writing. No need to synchronize in this case. } } if(needCreate) { createFile(theId); //Writes the file from the database lock(Coordination.Instance) Coordination.Instance.Remove[theId]; fileLock.ExitWriteLock(); fileLock = null; } if(fileLock != null) fileLock.EnterReadLock(); //read your data from the file if(fileLock != null) fileLock.ExitReadLock(); 

当然,不遵循此精确锁定协议的线程将可以访问该文件。

现在,锁定Singleton对象肯定不理想,但如果您的应用程序需要全局同步,那么这是实现它的一种方法。

你的问题真让我思考。

如果你使用了一个需要持久化的文件队列并让一个后台工作线程出列并持久化,那么如何让每个线程都负责文件访问并让它们阻塞呢?

当后台工作程序正在启动时,您可以让Web应用程序线程返回db值,直到文件确实存在。

我在GitHub上发布了一个非常简单的例子 。

随意给它一个机会让我知道你的想法。

仅供参考,如果您没有git,可以使用svn将其拉出来http://svn.github.com/statianzo/MultiThreadFileAccessWebApp

你为什么不只是使用数据库 – 例如,如果你有办法将文件名与它包含的数据库中的数据相关联,只需在数据库中添加一些信息,指定当前是否存在包含该信息的文件以及何时存在该文件。创建时,文件中的信息是多么陈旧等等。当一个线程需要一些信息时,它会检查数据库以查看该文件是否存在,如果不存在,它会向表中写出一行说它正在创建文件。 完成后,它会使用布尔值更新该行,表示该文件已准备好供其他人使用。

关于这种方法的好处 – 所有的信息都在一个地方 – 所以你可以做很好的错误恢复 – 例如,如果由于某种原因创建文件的线程严重死亡,另一个线程可以出现并决定重写文件,因为创建时间太旧了。 您还可以创建简单的批处理清理过程,并获得有关某些数据用于文件的频率,信息更新频率(通过查看创建时间等)的准确数据。 此外,您可以避免在整个文件系统中执行许多磁盘搜索,因为不同的线程会在整个地方查找不同的文件 – 尤其是如果您决定让多台前端计算机跨越公共磁盘。

棘手的事情 – 您必须确保您的数据库支持线程在创建文件时写入的表上的行级锁定,否则表本身可能会被锁定,这可能会使这个速度慢得令人无法接受。

这个问题很古老,而且已经有了明显的答案。 不过我想发布一个更简单的替代方案。

我想我们可以直接在文件名上使用lock语句,如下所示:

 lock(string.Intern("FileLock:absoluteFilePath.txt")) { // your code here } 

通常,由于String Interning,锁定字符串是个坏主意。 但在这种特殊情况下,它应该确保没有其他人能够访问该锁。 在尝试阅读之前,请使用相同的锁定字符串。 这里实习对我们有用而不是反对。

PS:文本‘FileLock’只是一些任意文本,以确保其他字符串文件路径不受影响。