Click here to Skip to main content
15,881,139 members
Please Sign up or sign in to vote.
4.50/5 (2 votes)
See more:
I need to gain thread-safe access to a Text-file.

I'm kinda new to Threading and I've tried many different solutions, but I'm still not having any success.

I thought I might Create a Class and call it say: ThreadSafeFileInstance.

In this class I'll have a Global private File Variable...
private File _file;

which I only give access to through a Property...
public File File
{
   get
   {
      return _file;                
   }
}


I thought I could use Mutex to do a "check-out" of the File Instance like this:

public File File
{
   get
   { 
      myMutex.WaitOne();
      return _file;    
      myMutex.ReleaseMutex(); 
   }
}


But this obviously wouldn't work, since the object calling the property will still have an instance of this file after the Mutex release the property.

Mutex blocks code in a method, I need something that can block the use of an object and cause other threads to wait when they access it and it's locked.

This class will be extended to contain all the basic File-editing methods like Read, Write, Replace etc... I'm really not sure how to use Mutex in this context.

Any beginners article you might recommend? Any advice is appreciated.
Posted

If you want to make a thread safe wrapper for a non-thread safe object then it's generally a bad idea to expose the non-thread safe object from the wrapper. Once you've exposed them it's very difficult to ensure that consumers use them in a thread safe way which defeats the purpose of the wrapper class. Instead of exposing the raw File object(s), add thread safe methods to the ThreadSafeFileInstance class that perform the thread safe operations for consumer classes. Example: you'd like to wrap a File in ThreadSafeFileInstance so you start with this:
C#
internal sealed class ThreadSafeFileInstance
{
    // used to synchronize access to the file
    private readonly object _fileLock = new object();
    // initialize elsewhere
    private FileStream _file;
}


If you expose the FileStream through a property then you also have to expose the lock object so that consumers can synchronize on it, but there's no way to guarantee that they actually do it. An alternative way would be to provide separate GetHandle, ReleaseHandle methods, but again it's difficult to enforce proper usage of those methods. What if a class calls GetHandle and then forgets to call ReleaseHandle?

Instead, provide methods that do thread safe operations:
C#
internal sealed class ThreadSafeFileInstance
{
    // used to synchronize access to the file
    private readonly object _fileLock = new object();
    // initialize elsewhere
    private FileStream _file;
    internal void Append(string data)
    {
        lock (_fileLock)
        {
            // inside the lock use _file
            _file.Write(Encoding.ASCII.GetBytes(data), 0, data.Length);
        }
    }
    // etc.
}



Also like Arun Jacob said, use a lock (which internally uses Monitors) instead of using Mutexes unless you absolutely need to use Mutexes. For protecting single resource inside of a single application a Mutex is overkill.
 
Share this answer
 
Comments
MatthysDT 21-Jul-10 10:09am    
Reason for my vote of 5
Great answer!
Create a static class for this purpose. Create an object inside that class and while accessing file, use lock,

lock(obj)
{
   // Do work here
}


or use Monitor class.

lock in C#[^]
 
Share this answer
 
v3
The release of the Mutex should be a separate call. The lock on an object as shown above won't help prevent this.

something like:
File GetFileHandle()
{
 myMutex.WaitOne();      
 return _file;      
}

void ReleaseFile()
{
 myMutex.ReleaseMutex(); 
}


a consumer should call both, like:

File f = GetFileHandle();
DoWork(f);
ReleaseFile();


To make it even more safe you could close and reopen the file to make sure the handle given to the consumer is rendered invalid.

Good luck!
 
Share this answer
 
Jimmanuel

Sorry for posting this as an Answer, not sure how to reply to an answer.

Thanks for explaining it to me, I more-or-less implemented your solution and it seems to work...(pending a stress test) I just have one more question...

I took Arun Jacob's advice and made the class static.

I removed the Global FileStream object and I created my thread-safe methods with Parameters to specify which file must be Appended, Deleted etc etc...

I used Global strings to point out these files' paths and an enumerator to specify which file should be "handled". So a typical (hopefully) ThreadSafe method of mine looks like this:

Where: statFile is my Enumerator and the LockObject_XXX variables are normal Objects.

public static string ReadFile(statFile pFile)
       {
           switch (pFile)
           {
               case statFile.Generated:
                   lock (LockObject_Generated)
                   {
                       return File.ReadAllText(pathGenerated);
                   }

               case statFile.Spooled:
                   lock (LockObject_Spooled)
                   {
                       return File.ReadAllText(pathSpooled);
                   }

               case statFile.Detected:
                   lock (LockObject_Detected)
                   {
                       return File.ReadAllText(pathDetected);
                   }

               case statFile.Finished:
                   lock (LockObject_Finished)
                   {
                       return File.ReadAllText(pathFeedback);
                   }

               case statFile.Feedback:
                   lock (LockObject_Feedback)
                   {
                       return File.ReadAllText(pathFinished);
                   }
           }
           return "";
       }


In what way is your internal sealed class and internal method better than this? (Other than being neater and easier to reference since it's an instance).
 
Share this answer
 
Comments
Jimmanuel 21-Jul-10 10:44am    
The two methods aren't inherently better or worse than one another, they're two different things to do two different jobs.

By default all of my classes start internal and sealed. I make them public only when I need to use them from another assembly and I remove the sealed only when I have another class the needs to inherit from it. It's just a policy of mine that I start with the most restrictive permissions and ease them only when necessary.

As for why my example was an instatiatable class instead of singleton, that wasn't the main focus of it. That was just the quickest way to put something demonstratable together.
Jimmanuel 21-Jul-10 10:49am    
And also, at the bottom of each answer is an "Add a Comment" button where you can leave a reply to a answer. The comment you post should be emailed to the author of the answer (I think). Unfortunately there doesn't seem to be a way to reply to a comment yet. :)
MatthysDT 21-Jul-10 11:21am    
Thank you, you've been a great help.
The stress test proved that the code is sound, no more IO access errors! Learned a lot today!
Cheers!

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900