Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Lock a file and avoid readings while it's writing

My web application returns a file from the filesystem. These files are dynamic, so I have no way to know the names o how many of them will there be. When this file doesn't exist, the application creates it from the database. I want to avoid that two different threads recreate the same file at the same time, or that a thread try to return the file while other thread is creating it.

Also, I don't want to get a lock over a element that is common for all the files. Therefore I should lock the file just when I'm creating it.

So I want to lock a file till its recreation is complete, if other thread try to access it ... it will have to wait the file be unlocked.

I've been reading about FileStream.Lock, but I have to know the file length and it won't prevent that other thread try to read the file, so it doesn't work for my particular case.

I've been reading also about FileShare.None, but it will throw an exception (which exception type?) if other thread/process try to access the file... so I should develop a "try again while is faulting" because I'd like to avoid the exception generation ... and I don't like too much that approach, although maybe there is not a better way.

The approach with FileShare.None would be this more or less:

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

But I don't like the fact that I have to catch exceptions, try several times and wait an inaccurate amount of time :|

like image 693
vtortola Avatar asked Apr 08 '10 14:04

vtortola


1 Answers

You can handle this by using the FileMode.CreateNew argument to the stream constructor. One of the threads is going to lose and find out that the file was already created a microsecond earlier by another thread. And will get an IOException.

It will then need to spin, waiting for the file to be fully created. Which you enforce with FileShare.None. Catching exceptions here doesn't matter, it is spinning anyway. There's no other workaround for it anyway unless you P/Invoke.

like image 107
Hans Passant Avatar answered Oct 02 '22 18:10

Hans Passant