Ajit Goel
Ajit Goel

Reputation: 4388

The process cannot access the file because it is being used by another process: Correct way to read\write to a file: heavily used application-Part II

We have a heavily used .Net 3.5 application that reads "expensive to create" data and caches it. However we are getting a lot of errors around both reading the cache file and writing to the cache file. Some of the advice that I have got from StackOverflow forums is to:

Is this a correct way of reading and writing files? Please advise.

private XmlDocument ReadFromFile(string siteID, Type StuffType)
{
   XmlDocument result = null;
   var fsPath = FileSystemPath(siteID, StuffType.Name);
   result = new XmlDocument();
   using (var streamReader = new StreamReader(fsPath))
   //using (var fileStream = new FileStream(fsPath, FileMode.Open, FileAccess.Read, FileShare.Read))
   {
      result.Load(streamReader);
   }
   //GC.Collect();                
   return result;
}

private readonly object thisObject = new object();
private void WriteToFile(string siteID, XmlDocument stuff, string fileName)
{
   var fsPath = FileSystemPath(siteID, fileName);
   lock (thisObject)
   {
      //using (var fileStream = new FileStream(fsPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
      using (var streamWriter = new StreamWriter(fsPath))
      {
         stuff.Save(streamWriter);
      }
      //GC.Collect();
    }
 }

Upvotes: 1

Views: 8489

Answers (3)

Martin Mulder
Martin Mulder

Reputation: 12954

If you want synchronize access to resource there are several options, depending on the context. There a several (generic) situations:

  1. Single process, single thread

    No synchronization required.

  2. Single process, multiple threads

    Use a simple locking mechanism like lock or ReaderWriterLockSlim.

  3. Multiple processes, single machine

    Use a (named) Mutex. A Mutex is not very fast. More about performance at the bottom.

  4. Multiple processes, multiple machines

    Now it starts getting interested. .NET does not have an in-the-box solution for this. I can think of two solutions:

    • The try-again-method: Make an while-loop with a try-catch inside. Let him do the resource operation in the try-scope. If it succeeds, then return a successful result. If it fails, wait a few milliseconds and try again... and again... and again.
    • The synchronization master: Make a webservice running on a central location in the network. All process who want access to the resource first have to ask the service for permission. If the resource is "locked" the service will wait, resulting in that the process will wait. As soon as the resource is released, the service is informed and will allow the next process in line to access the resource.

In this case
Of course this last solution is a generic solution. In the case of Ajit Goel it would be as simple as creating a centralized service to read/write files. Then you have one filemaster which is in control of iets files.

Another solution could be to store all your files inside a central database and let him do all the synchronization.

Performance
If performance starts to be an issue, you could try to create a cache.

  1. You could create a cache in memory (but with a lot of files or large files, that could become a memory issue).
  2. You could create a cache in a local folder (one per each process). As soon as the centralized location is modified (just verify the dates), you can copy that file (with a Mutex lock) to your own local folder. Form there your can read the files over and over without a lock with read-access and read-sharing.

Upvotes: 7

Scott Mermelstein
Scott Mermelstein

Reputation: 15397

I think that the ReaderWriterLock in combination with FileShare.ReadWrite is your solution. (Note, the document page I'm linking you to refers you to a better version called ReaderWriterLockSlim, which should be at least as good.)

You need FileShare.ReadWrite on each thread, so they can all access it however necessary. Any time a thread needs to read, have it AcquireReaderLock (and ReleaseReaderLock when the read is complete).

When you want to write, just use UpgradeToWriterLock, and when you're done, DowngradeFromWriterLock.

This should let all your threads access the file read-only at all times, and let any one thread grab the access to write whenever necessary.

Hope that helps!

Upvotes: 1

AaronLS
AaronLS

Reputation: 38367

I believe everyone has to open the file in FileShare.ReadWrite mode.

If someone opens it in FileShare.Read mode, and someone else tries to write to it, it will fail. I don't think they are compatible, because one is saying "share for read only", but the other wants to write. You may need to use FileShare.ReadWrite on all of them, OR minimize the amount of writing to minimize the conflicts.

http://msdn.microsoft.com/en-us/library/system.io.fileshare.aspx

Another option is to use FileShare.Read, and copy the file when making modifications. If there is a single entry point for all modifications, then it can use FileShare.Read to copy the file. Modify the copy, and then update some sort of variable/property that indicates the current file path. Once that is updated, all other processes reading from the file would use that new location. This would allow the modification to occur and complete, and then make all of the readers aware of the new modified file. Again, only viable if you can centralize the modifications. Maybe through some sort of modification request queue if necessary.

Upvotes: 0

Related Questions