Sunday, September 10, 2023

Asynchronous await gotcha with Mutex synchronization

I ran into an issue today that took me a while to figure out what was happening so I thought I'd share.

I wanted to take advantage of the asynchronous function: NamedPipeServerStream WaitForConnectionAsync to create a multi client Named Pipe server.  I also wanted to prevent other instances of the server running so I used a Mutex like this:

//Named pipe server run method

public async Task Run()
{
     using (var mutex = new Mutex(true, mutexName, out var createdNew))
            {
                if (createdNew)
                {
                    try
                    {
                        await RunInternal();
                    }
                    finally
                    {
                        mutex.ReleaseMutex();
                    }
                }
            }
}


Everything looked and ran normally until the RunInternal method completed.  The call to ReleaseMutex was throwing a synchronization exception:

ApplicationException: Object synchronization method was called from an unsynchronized block of code.

I didn't realize (or appreciate) that the await keyword returns control on a different thread.  Although method looks continuous, the Mutex was right to complain that a thread that didn't create the Mutex was trying to release it.

Once I understood what was going on, the fix was easy;  Use the synchronous Wait instead.  Note:  I also changed the method to be non-async.

public void Run()
{
       using (var mutex = new Mutex(true, mutexName, out var createdNew))
       {
         if (createdNew)
         {
           try
           {

             //Synchronously wait so that the finally block executes in current thread.
             //This is a requirement of the Mutex.ReleaseMutex method.
             RunInternal().Wait();
           }
           finally
           {
             mutex.ReleaseMutex();
           }
         }
       }
     }
}

Maybe there's better solution but this worked for me, much more to learn I'm sure.