tags:

views:

84

answers:

4

I need to open a file but if it's currently not available I need to wait until it's ready. What's the best approach to take?

SCENARIO

I'm using files as a persistent caching mechanism for application data. This data needs to be read and deserialized often (written only once, and deleted occasionally). I have a cleanup process that runs on a separate thread that determines which files are no longer needed and deletes them. Opening and reading of files may happen concurrently (rarely, but could happen) and I want the process to wait and try to read the data again.

Thanks!

A: 

Like all "what is the best approach" questions, this one depends on your needs. Some options that come easily to mind:

  1. Abort the attempt
  2. Loop until the file becomes unlocked
  3. Ask the user what to do about it

Which one you chose depends on how you can deal with it.

Tergiver
@Tergiver, 1) is not a very effective means of waiting for the lock to become available. 3) is not a programmatic solution, though perhaps the correct one. And 2) is, I suspect, the default solution upon which Micah is trying to improve.
Kirk Woll
I would point out that the original question looked nothing like its present form.
Tergiver
+1  A: 

It depends on who controls the file. If one part of your application needs to wait until another part of the application finishes preparing the file, then you can use a ManualResetEvent. That is, at startup, your program creates a new event:

public ManualResetEvent FileEvent = new ManualResetEvent(false);

Now, the part of the program that's waiting for the file has this code:

FileEvent.WaitOne();

And the part of the program that's creating the file does this when the file's ready:

FileEvent.Set();

If your application has to wait for a file that is being used by another application that you have no control over, your only real solution is to continually try to open the file.

FileStream f = null;
while (f == null)
{
    try
    {
        f = new FileStream(...);
    }
    catch (IOException)
    {
        // wait a bit and try again
        Thread.Sleep(5000);
    }
}
Jim Mischel
+1  A: 

I'm not a huge fan of the try/catch IOException because:

  1. The reason for the exception is unknown.
  2. I dislike 'expected' exceptions as I often run with break on excpetion.

You can do this without exceptions by calling CreateFile and returning a stream when/if it finally returns a handle:

public static System.IO.Stream WaitForExclusiveFileAccess(string filePath, int timeout)
{
    IntPtr fHandle;
    int errorCode;
    DateTime start = DateTime.Now;

    while(true)
    {
        fHandle = CreateFile(filePath, EFileAccess.GenericRead | EFileAccess.GenericWrite, EFileShare.None, IntPtr.Zero,
                             ECreationDisposition.OpenExisting, EFileAttributes.Normal, IntPtr.Zero);

        if (fHandle != IntPtr.Zero && fHandle.ToInt64() != -1L)
            return new System.IO.FileStream(fHandle, System.IO.FileAccess.ReadWrite, true);

        errorCode = Marshal.GetLastWin32Error();

        if (errorCode != ERROR_SHARING_VIOLATION)
            break;
        if (timeout >= 0 && (DateTime.Now - start).TotalMilliseconds > timeout)
            break;
        System.Threading.Thread.Sleep(100);
    }


    throw new System.IO.IOException(new System.ComponentModel.Win32Exception(errorCode).Message, errorCode);
}

#region Win32
const int ERROR_SHARING_VIOLATION = 32;

[Flags]
enum EFileAccess : uint
{
    GenericRead = 0x80000000,
    GenericWrite = 0x40000000
}

[Flags]
enum EFileShare : uint
{
    None = 0x00000000,
}

enum ECreationDisposition : uint
{
    OpenExisting = 3,
}

[Flags]
enum EFileAttributes : uint
{
    Normal = 0x00000080,
}

[DllImport("kernel32.dll", EntryPoint = "CreateFileW", SetLastError = true, CharSet = CharSet.Unicode)]
static extern IntPtr CreateFile(
   string lpFileName,
   EFileAccess dwDesiredAccess,
   EFileShare dwShareMode,
   IntPtr lpSecurityAttributes,
   ECreationDisposition dwCreationDisposition,
   EFileAttributes dwFlagsAndAttributes,
   IntPtr hTemplateFile);

#endregion
csharptest.net
A: 

I am not sure how complex your data is. Try creating a class that represents the given file (and the associated data) on the disk. The class instances would be stored in a collection class instance, probably a Dictionary< > with a key for fast retrieval. Note that in the multi-threaded scenario you may have to synchronize access to the collection. The class would have properties to represent the data in the file, a Write method to send the data to disk, a static Read method which would create a new instance of the class where the constructor would read the file from the disk and store the data (access to the data is via the properties), and a Delete method that would remove the file from the disk. You could then use standard synchronization methods (i.e. a lock { ... } in the Read and Delete methods to prevent the problem you are hitting with one thread trying to read while another thread is deleting.

Steve Ellinger