Solution 1:

You must ensure that mutex is being accessed consistently on a certain thread. You could do that in a number of ways:

  1. Do not use await in the critical section during which you hold the mutex
  2. Invoke the mutex calls on a TaskScheduler that only has a single thread

That could look like this:

await Task.Factory.StartNew(() => mutex.WaitOne(), myCustomTaskScheduler);

Or, you use synchronous code and move everything to the thread-pool. If you only have access to an async version of DoSomething, consider just calling Task.Wait on its result. You'll suffer a minor inefficiency here. Probably fine.

Solution 2:

I use named Mutex in async method to control that only one process call it. Another process checks for named Mutex and exits if it cannot create new named Mutex.

I can use named Mutex in async method as OS guarantees/controls only one instance of named object in OS. Plus, I do not use WaitOne/Release that should be called on the thread.

public async Task<bool> MutexWithAsync()
{
    // Create OS-wide named object. (It will not use WaitOne/Release)
    using (Mutex myMutex = new Mutex(false, "My mutex Name", out var owned))
    {
        if (owned)
        {
            // New named-object was created. We own it.
            try
            {
                await DoSomething();
                return true;
            }
            catch
            {
                return false;
            }
        }
        else
        {
            // The mutex was created by another process.
            // Exit this instance of process.
        }
    }
}

Solution 3:

You can use a binary Semaphore instead of a Mutex. A Semaphore does not need to be release by the same thread that acquired it. The big disadvantage here is if the application crashes or is killed within DoSomething() the semaphore will not be released and the next instance of the app will hang. See Abandoned named semaphore not released

 public async Task<bool> MutexWithAsync()
 {
     using (Semaphore semaphore = new Semaphore(1, 1, "My semaphore Name"))
     {
         try
         {
             semaphore.WaitOne();
             await DoSomething();
             return true;
         }
         catch { return false; }
         finally { semaphore.Release(); }
     }
 }