overview

Object –> CriticalFinalizerObject –> Thread

synchronizing access to shared resources

Monitor

Synchronize access to objects.

  • The Monitor class allows you to synchronize access to a region of code by taking and releasing a lock on a particular object by calling the Monitor.Enter, Monitor.TryEnter, and Monitor.Exit methods.
  • You can also use the Monitor class to ensure that no other thread is allowed to access a section of application code being executed by the lock owner, unless the other thread is executing the code using a different locked object.
  • Documentation: https://docs.microsoft.com/en-us/dotnet/api/system.threading.monitor?view=net-6.0

Lock

The Lock class is used to define regions of code that require mutually exclusive access between threads of a process. These regions are called critical sections. Locks are entered and exited, and the code between the enter and exit is the critical section. A thread that enters a lock is said to hold or own the lock until it exits the lock. Only one thread can hold a lock at any given time.

Lock objects have an Enter method:

  • When the method returns, the current thread is the only thread that holds the lock.
  • When the lock cannot be immediately entered, it waits.

Lock objects have a TryEnter method:

  • The method returns boolean if the lock was entered by the current thread.
  • When the method returns true, the current thread is the only thread that holds the lock.
  • When the lock cannot be immediately entered, it returns false.

For both Enter and TryEnter:

  • These methods may throw a LockRecursionException if the lock reaches a limit of repeated entries by the current thread.
  • If the current thread already holds the lock, the lock is entered again.

⚠️ Warning

To fully exit the lock, the current thread must exit the lock as many times as it entered it.

Lock objects have an EnterScope method:

  • The method returns a Lock.Scope that can be disposed of to exit the lock.
  • When the lock cannot be immediately entered, it waits.
  • This method is intended to be used with a using statement.

Examples:

public sealed class ExampleDataStructure
{
    private readonly Lock _lockObj = new();

    public void Modify()
    {
        lock (_lockObj)
        {
            // Critical section associated with _lockObj
        }

        using (_lockObj.EnterScope())
        {
            // Critical section associated with _lockObj
        }

        // The Enter() method enters the lock, waiting if necessary:
        _lockObj.Enter();
        try
        {
            // Critical section associated with _lockObj
        }
        finally { _lockObj.Exit(); }

        // The TryEnter() method enters the lock without waiting:
        if (_lockObj.TryEnter())
        {
            try
            {
                // Critical section associated with _lockObj
            }
            finally { _lockObj.Exit(); }
        }
    }
}

lock statement

The lock statement works like a using statement with the EnterScope method of a Lock.

So, this…

lock (x)
{
    // ...
}

…is equivalent to this:

using (x.EnterScope())
{
    // ...
}

Semaphore

Use the Semaphore class to control access to a pool of resources.

Mutex

A synchronization primitive that can also be used for inter-process synchronization.

Interlocked

Provides atomic operations for variables that are shared by multiple threads.