简体   繁体   中英

C# - locking in threads with mixed file and folder usage (thread safety)

I wanted to ask if there is a special mechanism in C# or .net which can do the following:

I have an multi threading application and I want to prevent threads doing something on the same file/folder. like the lock(Object obj) command it should block if an other thread is changing something on this file or folder or a sub folder.

For example:
All threads starting at the same time (ok pseudo same time ;) ).
Thread A: uses folder1\\folder2\\file.txt and should lock it with an mechanism.
Thread B: uses folder1\\file2.txt and should open another lock
Thread C: wants to rename folder1 and should be blocked as long as Thread A and Thread B releases the lock.
Thread D: renames folder3 and opens a lock wich blocks nothing.
Just an example what I'd like to see...

It should be possible to have unlimited hierachical dependencies. I know there are some mechanisms for supporting file locking and maybe there is something to also lock folders with sub folders and prevent parent changes. If there is nothing then I have to implement it on my own.

This may sound like magic. But you can actually lock on strings.

lock (@"c:\file.txt") {
   // Do something
}

The reason is how .Net handles strings internally. Normally any two strings that contains the same data will be a reference to the same object. Alternatively you can just add them to a Dictionary (keys are unique entries) and use the value (object) to lock.

Here is a simple example:

Dictionary<string, object> LockObjects = new Dictionary<string, object>();
void DoStuff(string file) {
  // Lock while modifying LockObjects
  string lockObj = null;
  lock (LockObjects) {
    if (!LockObjects.Contains(file))
      LockObjects.Add(file, new Object());
    lockObj = LockObjects[file];
  }
  lock (lockObj) {
    // Do stuff to the file
  }
}

lock () will also wait for others to finish, so any thread can safely lock and wait.

In your program logic you MUST make sure you don't open up for deadlock scenarios. For example Thread1 locks File1. Thread2 opens File2. T1 won't continue until it gets a lock on F2. T2 won't continue until it gets a lock on F1. Both will wait forever. This has to be avoided by design!

You can lock while opening and closing a file, and writing your "file lock" data. Threads that run into a locked file, can wait for a signal, for instance:

lock(somelock)
{
  while (MyLockedFiles.Contains(thefile))
  {
    // check again as soon as a file closes
    Monitor.Wait(somelock);
  }
  // Open the file
  MyLockedFiles.Add(thefile);
}

And when closing the file:

lock(somelock)
{
  MyLockedFiles.Remove(thefile);
  // make blocked threads check the MyLockedFiles list
  Monitor.PulseAll(somelock);
}

All that in a File I/O wrapper class, so you only open files on one spot.

Here is my quick implementation of the problem. Feedback welcome.

class LockManager
{
    private static Object managerLock = new Object();

    private static Dictionary<string, int> fileLocks = new Dictionary<string, int>();
    private static Dictionary<string, int> folderLocks = new Dictionary<string, int>();



    public static void EnterFolderLock(string folderPath)
    {
        Monitor.Enter(managerLock);

        string aLocked;

        // test if all subfolders are not locked
        do
        {
            aLocked = null;

            foreach (KeyValuePair<string, int> folderLock in folderLocks)
            {
                // if it is in a locked folder
                if (folderPath.Contains(folderLock.Key))
                {
                    aLocked = folderLock.Key;
                    break;
                }
                else if (folderLock.Key.Contains(folderPath))
                {
                    aLocked = folderLock.Key;
                    break;
                }
            }


            if (aLocked == null)
            {
                foreach (KeyValuePair<string, int> fileLock in fileLocks)
                {
                    // if it has a locked file
                    if (fileLock.Key.Contains(folderPath))
                    {
                        aLocked = fileLock.Key;
                        break;
                    }
                }
            }



            if (aLocked != null)
            {
                Monitor.Exit(managerLock);

                // wait until the lock was released
                Monitor.Enter(aLocked);
                Monitor.Exit(aLocked);

                Monitor.Enter(managerLock);
            }
        } while (aLocked != null);


        if (folderLocks.ContainsKey(folderPath))
        {
            folderLocks[folderPath] = folderLocks[folderPath] + 1;
        }
        else
        {
            folderLocks.Add(folderPath, 1);
        }

        Monitor.Exit(managerLock);

        // get the file lock
        Monitor.Enter(folderPath);

    }


    public static void EnterFileLock(string filePath)
    {
        Monitor.Enter(managerLock);

        string aLockedFolder;

        // test if all subfolders are not locked
        do
        {
            aLockedFolder = null;

            foreach (KeyValuePair<string, int> folderLock in folderLocks)
            {
                // if it is in a locked folder
                if (filePath.Contains(folderLock.Key))
                {
                    aLockedFolder = folderLock.Key;
                    break;
                }
            }



            if (aLockedFolder != null)
            {
                Monitor.Exit(managerLock);

                // wait until the lock was released
                Monitor.Enter(aLockedFolder);
                Monitor.Exit(aLockedFolder);

                Monitor.Enter(managerLock);
            }
        } while (aLockedFolder != null);


        if (fileLocks.ContainsKey(filePath))
        {
            fileLocks[filePath] = fileLocks[filePath] + 1;
        }
        else
        {
            fileLocks.Add(filePath, 1);
        }

        Monitor.Exit(managerLock);

        // get the file lock
        Monitor.Enter(filePath);

    }


    public static void ExitFolderLock(string folderPath)
    {
        Monitor.Enter(managerLock);

        if (!folderLocks.ContainsKey(folderPath))
        {
            // key is missing!!!
            throw new Exception("Can't exit this folder lock if it's not open!");
        }
        else if (folderLocks[folderPath] == 0)
        {
            // key is missing!!!
            throw new Exception("Can't exit this folder lock if it's not open! Something strange happened. Please debug.");
        }
        else if (folderLocks[folderPath] == 1)
        {
            // last entry remove
            folderLocks.Remove(folderPath);
        }
        else
        {
            folderLocks[folderPath] = folderLocks[folderPath] - 1;
        }

        Monitor.Exit(folderPath);

        Monitor.Exit(managerLock);
    }


    public static void ExitFileLock(string filePath)
    {
        Monitor.Enter(managerLock);

        if (!fileLocks.ContainsKey(filePath))
        {
            // key is missing!!!
            throw new Exception("Can't exit this file lock if it's not open!");
        }
        else if (fileLocks[filePath] == 0)
        {
            // key is missing!!!
            throw new Exception("Can't exit this file lock if it's not open! Something strange happened. Please debug.");
        }
        else if (fileLocks[filePath] == 1)
        {
            // last entry remove
            fileLocks.Remove(filePath);
        }
        else
        {
            fileLocks[filePath] = fileLocks[filePath] - 1;
        }

        Monitor.Exit(filePath);

        Monitor.Exit(managerLock);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM