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

时间:2010-04-08 14:01:07

标签: c# .net multithreading synchronization filestream

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

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

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

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

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

使用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);
            }
        }
    }

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

6 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分:1)

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

//somewhere on your code or put on a singleton
static  System.Collections.Generic.HashSet<String> filesAlreadyProcessed= new  System.Collections.Generic.HashSet<String>();


//thread main method code
bool filealreadyprocessed = false
lock(filesAlreadyProcessed){
  if(set.Contains(filename)){
    filealreadyprocessed= true;
  }
  else{
     set.Add(filename)
  }
}
if(!filealreadyprocessed){
//ProcessFile
}

答案 2 :(得分:1)

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

假设这些文件中的每一个都对应于数据库中的唯一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对象肯定不理想,但如果您的应用程序需要全局同步,那么这是实现它的一种方法。

答案 3 :(得分:1)

你的问题确实让我思考。

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

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

我发布了一个非常简单的example of this on GitHub

随意试一试,让我知道你的想法。

仅供参考,如果您没有git,可以使用svn来提取它http://svn.github.com/statianzo/MultiThreadFileAccessWebApp

答案 4 :(得分:0)

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

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

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

答案 5 :(得分:0)

问题是陈旧的,已经有了明显的答案。不过我想发布一个更简单的替代方案。

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

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

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

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