SoFunction
Updated on 2025-03-06

Thread synchronization between c# processes

The Mutex class, Event class, SemaphoreSlim class, ReaderWriterLockSlim class, etc. provide thread synchronization between multiple processes.

1. WaitHandle base class

WaitHandle abstract class, used to wait for a signal to set. You can wait for different signals according to the different derived classes. The asynchronous delegated BeginInvoke() method returns an object that implements the IAsyncResult interface. Use the IAsyncResult interface to access the WaitHandle base class using the AsyncWaitHandle property. When calling the WaitOne() method, the thread will wait for a signal related to the wait handle:

static void Main(string[] args)
{
  Func<int> func = new Func<int>(
    () =>
    {
      (1500);
      return 1;
    });
  IAsyncResult ar = (null, null);
  int count = 0;
  while (true)
  {
    (ref count);
    ("The{0}Periodic cycle waiting for results。", count);
    if ((100, false))
    {
      ("Get back results.");
      break;
    }
  }
  int result = (ar);
  ("The result is:{0}", result);
}

Use the WaitHandle base class to wait for a signal to appear (WaitHandle() method), wait for multiple objects to send a signal (WaitAll() method), and wait for any of the multiple objects to send a signal (WaitAny() method). The WaitAll() method and WaitAny() method are used to receive a WaitHandle parameter array.

The SafeWaitHandle property of the WaitHandle base class, where a native handle can be assigned to a system resource, waiting for the handle, such as I/O operations, or a custom handle.

2. Mutex class

The Mutex class inherits from the WaitHandle class and provides a class that is accessed simultaneously across multiple processes. Similar to the Monitor class, only one thread has a lock. The meaning of each parameter in the constructor of Mutex class:

  • initiallyOwned: If true, the initial ownership of the system mutex that has been named by the calling thread (if the named system mutex was created with this call); otherwise, false.
  • name: The name of the system mutex. If the value is null, it is unnamed.
  • createdNew: When this method returns, if a local mutex is created (i.e., if name is null or empty string) or a specified named system mutex, it contains a boolean true; if the specified named system mutex already exists, it is false. This parameter is passed without initialization.
  • mutexSecurity: An object that represents the access control security applied to a named system mutex.

Mutexes can also be defined in another process, and the operating system can recognize names of mutexes, which are shared between processes. If no mutually exclusive name is specified, it is not shared among different processes. This method can detect whether the program has been run and can prohibit the program from being started twice.

static void Main(string[] args)
{
  // ThreadingTimer();
  // TimersTimer();
  bool isCreateNew = false;
  Mutex mutex = new Mutex(false, "MyApp", out isCreateNew);//Query whether there is a mutually exclusive "MyApp" already exists  if(isCreateNew==false)
  {
    // Mutual Exclusion already exists  }
}

To turn on an existing mutex, you can use the() method, without the same .Net permissions required when creating a mutex. You can use the WaitOne() method to obtain a mutually exclusive lock and become the possession of the mutually exclusive. Call the ReleaseMutex() method to release the mutex:

if(())//Set mutex lock{
  try
  {
    //Execute the code  }
  finally {
    ();//Release Mutual Exclusion  }
}
else
{
  //There is a problem}

3. Semaphore class

Semaphores are a counting mutex lock that can be used by multiple threads at the same time. Semaphores can define the number of threads that allow access to resources protected by flag-speak locks at the same time. Semaphore and SemaphoreSlim have semaphore functions. The Semaphore class can specify a name to make it look up within the scope of the system resource, allowing synchronization between different processes. The Semaphore class is a lightweight version that optimizes for shorter waiting times.

static void Main()
{
  int taskCount = 6;
  int semaphoreCount = 3;
  Semaphore semaphore = new Semaphore(semaphoreCount, semaphoreCount, "Test");//Create a semaphore with a count of 3  /* The first parameter is the number of locks that are initially released, and the second parameter is the total number of lockables.  If the first parameter is smaller than the second parameter, its difference is the number of measurements of the assigned threads.
    * The third parameter is the name specified by the signal, which allows it to be shared among different processes.
    */
  var tasks = new Task[taskCount];

  for (int i = 0; i < taskCount; i++)
  {
    tasks[i] = (() => TaskMain(semaphore));//Create 6 tasks  }

  (tasks);

  ("All tasks finished");
}

//The task of locking the signalstatic void TaskMain(Semaphore semaphore)
{
  bool isCompleted = false;
  while (!isCompleted)//Cycling for the released signal quantity  {
    if ((600))//The maximum waiting time is 600ms    {
      try
      {
        ("Task {0} locks the semaphore", );
        (2000);//Release the signal after 2s      }
      finally
      {
        ("Task {0} releases the semaphore", );
        ();//Release the semaphore        isCompleted = true;
      }
    }
    else
    {
      //Write a timeout waiting information after the specified waiting time is exceeded.      ("Timeout for task {0}; wait again", );
    }
  }
}

In the above method, the semaphore count is 3, so at most only three tasks can be locked, and the 4th and later tasks must be waited. When unlocking, you must unlock the resource under any circumstances.

4. Events class

Events are also a system-wide resource synchronization method. It is mainly provided by the following classes: ManualResetEvent, AutoResetEvent, ManualResetEventSlim, and CountdownEvent classes.

In the ManualResetEventSlim class, calling the Set() method can send a signal; calling the Reset() method can reset to the signalless state. If multiple threads are waiting to signal an event and call the Set() method, all waiting threads are released. If a thread has just called the WiatOne() method but the event has signaled, the waiting thread can continue to wait.

In the AutoResetEvent class, the signal can also be emitted through the Set() method and the Reset() method reset signal, but this class automatically resets the signal. If a thread is waiting for an automatically reset event to signal, when the waiting state of the first thread ends, the event will automatically become a non-signal state. That is: if multiple threads are waiting to signal an event, only one thread ends its waiting state, it is not the thread that waits for the longest event, but the thread that has the highest priority.

//A class that calculates data, example using ManualResetEventSlim classpublic class Calculator
{
  private ManualResetEventSlim mEvent;
  public int Result { get; private set; }
  public Calculator(ManualResetEventSlim ev)
  {
     = ev;
  }
  public void Calculation(int x, int y)
  {
    ("Task {0} starts calculation", );
    (new Random().Next(3000));//Random waiting for events    Result = x + y;// Calculation results
    ("Task {0} is ready", );
    ();//Send a completion signal  }
}
//Example of external calls:static void Main()
{
  const int taskCount = 10;

  ManualResetEventSlim[] mEvents = new ManualResetEventSlim[taskCount];

  WaitHandle[] waitHandles = new WaitHandle[taskCount];
  var calcs = new Calculator[taskCount];

  for (int i = 0; i < taskCount; i++)
  {
    int i1 = i;//The purpose is to make the task to be executed later without waiting for execution before releasing i, so that for continue    mEvents[i] = new ManualResetEventSlim(false);//The corresponding task's event object sends a signal    waitHandles[i] = mEvents[i].WaitHandle;//The ManualResetEvent class is derived from the WaitHandle class, but ManualResetEventSlim is not, so its WaitHandle object needs to be saved    calcs[i] = new Calculator(mEvents[i]);

    (() => calcs[i1].Calculation(i1 + 1, i1 + 3));
  }

  for (int i = 0; i < taskCount; i++)
  {
    int index = (waitHandles);//Waiting for any signal to be sent and returning the index of the signal to be sent    if (index == )
    {
      ("Timeout!!");
    }
    else
    {
      mEvents[index].Reset();//Reset to signal-free state      ("finished task for {0}, result: {1}", index, calcs[index].Result);
    }
  }
}

The CountdownEvent class is suitable for: you need to assign a work task to multiple tasks, and then merge the results after each task is finished (no need to create an event object for each task separately). Each task does not need to be synchronized. The CountdownEvent class defines an initial number for all tasks that have events set. After reaching this count, it sends a signal.

//Modify the calculation classpublic class Calculator
{
  private CountdownEvent cEvent;
  public int Result { get; private set; }
  public Calculator(CountdownEvent ev)
  {
     = ev;
  }
  public void Calculation(int x, int y)
  {
    ("Task {0} starts calculation", );
    (new Random().Next(3000));//Random waiting for events    Result = x + y;// Calculation results    // signal the event—completed!
    ("Task {0} is ready", );
    ();//Send a completion signal  }
}
//Modify method callstatic void Main()
{
  const int taskCount = 10;
  CountdownEvent cEvent = new CountdownEvent(taskCount);

  WaitHandle[] waitHandles = new WaitHandle[taskCount];
  var calcs = new Calculator[taskCount];

  for (int i = 0; i < taskCount; i++)
  {
    int i1 = i;//The purpose is to make the task to be executed later without waiting for execution before releasing i, so that for can continue    calcs[i] = new Calculator(cEvent);// Assign this event notification class to each task    (() => calcs[i1].Calculation(i1 + 1, i1 + 3));

  }

  ();//Waiting for a signal of an event  ("all finished");
  for (int i = 0; i < taskCount; i++)
  {
    ("task for {0}, result: {1}", i, calcs[i].Result);
  }
}

5. Barrier Class

The Barrier class is suitable for: work has multiple task branches and needs to be merged after all tasks are executed. Unlike CountdownEvent, this class is used for participants who need to be synchronized. After activating a task, other participants can be added dynamically. All other participants can be waited for the completion of the work before the main participant continues.

static void Main()
{
  const int numberTasks = 2;
  const int partitionSize = 1000000;
  var data = new List<string>(FillData(partitionSize * numberTasks));
  var barrier = new Barrier(numberTasks + 1);//Define three participants: one main participant (assigned tasker), two sub-participants (assigned tasker)  var tasks = new Task<int[]>[numberTasks];//Two sub-participants  for (int i = 0; i < numberTasks; i++)
  {
    int jobNumber = i;
    tasks[i] = (() => CalculationInTask(jobNumber, partitionSize, barrier, data));//Start the calculation task: it can be written separately to perform multiple different tasks.  }
  ();// The main participant completes, waiting for the child participant to complete.  //Merge two results (LINQ)  IEnumerable<int> resultCollection = tasks[0].(tasks[1].Result, (c1, c2) => { return c1 + c2; }).ToList();//Summon now
  char ch = 'a';
  int sum = 0;
  foreach (var x in resultCollection)
  {
    ("{0}, count: {1}", ch++, x);//Output result    sum += x;
  }
  ("main finished {0}", sum);//The number of strings counted  ("remaining {0}, phase {1}", , );//Current participant information}

static int[] CalculationInTask(int jobNumber, int partitionSize, Barrier barrier, IList<string> coll)
{
  var data = new List<string>(coll);
  int start = jobNumber * partitionSize;// Calculate the subscript  int end = start + partitionSize;//The calculation ends  ("Task {0}: partition from {1} to {2}", , start, end);
  int[] charCount = new int[26];
  for (int j = start; j < end; j++)
  {
    char c = data[j][0];//Get the first character of the current string    charCount[c - 97]++;//The number of corresponding characters +1;  }
  ("Calculation completed from task {0}. {1} times a, {2} times z", , charCount[0], charCount[25]);//Inform the calculation to be completed  ();//Inform, reduce the number of parameters  ("Task {0} removed from barrier, remaining participants {1}", , );
  return charCount;//Return the statistical results}

// Used to fill a string link tablepublic static IEnumerable<string> FillData(int size)
{
  var data = new List<string>(size);
  var r = new Random();
  for (int i = 0; i < size; i++)
  {
    (GetString(r));//Get a random string  }
  return data;
}
private static string GetString(Random r)
{
  var sb = new StringBuilder(6);
  for (int i = 0; i < 6; i++)
  {
    ((char)((26) + 97));//Create a random string of 6 characters  }
  return ();
}

6. ReaderWriterLockSlim class

This class is to make the locking mechanism allow multiple readers to be locked (rather than one writer) to access a resource: if no writer locks the resource, multiple readers are allowed to access the resource, but only one writer can lock the resource.

Its properties can read whether it is in a blocked or not locked lock, such as EnterReadLock() and TryEnterReadLock() methods. You can also get whether it is in a write-locked or non-locked state, such as EnterWriteLock() and TryEnterWriteLock() methods. If the task needs to read the resource first and then write the resource, you can use the EnterUpgradeableReadLock() or TryEnterUpgradeableReadLock() method to obtain the upgradeable read lock. This lock can acquire a write lock without releasing the read lock.

class Program
{
  private static List<int> items = new List<int>() { 0, 1, 2, 3, 4, 5 };
  private static ReaderWriterLockSlim rwl = new ReaderWriterLockSlim();

  static void ReaderMethod(object reader)
  {
    try
    {
      ();
      for (int i = 0; i < ; i++)
      {
        ("reader {0}, loop: {1}, item: {2}", reader, i, items[i]);
        (40);
      }
    }
    finally
    {
      ();
    }
  }

  static void WriterMethod(object writer)
  {
    try
    {
      while (!(50))
      {
        ("Writer {0} waiting for the write lock", writer);
        ("current reader count: {0}", );
      }
      ("Writer {0} acquired the lock", writer);
      for (int i = 0; i < ; i++)
      {
        items[i]++;
        (50);
      }
      ("Writer {0} finished", writer);
    }
    finally
    {
      ();
    }
  }

  static void Main()
  {
    var taskFactory = new TaskFactory(, );
    var tasks = new Task[6];
    tasks[0] = (WriterMethod, 1);
    tasks[1] = (ReaderMethod, 1);
    tasks[2] = (ReaderMethod, 2);
    tasks[3] = (WriterMethod, 2);
    tasks[4] = (ReaderMethod, 3);
    tasks[5] = (ReaderMethod, 4);

    for (int i = 0; i < 6; i++)
    {
      tasks[i].Wait();
    }
  }
}

The above is the detailed content of thread synchronization between c# processes. For more information about c# thread synchronization, please pay attention to my other related articles!