1

I'm trying to use C# mutex to ensure my application is running in single instance. So, the application is acquiring global mutex on start, and releasing it on exit. If the mutex acquisition is failed, the error is shown. The application is a console app and it's mostly asynchronous (Main method is async). Basically, the mutex is acquired at the beginning of Main method, and released at the end of it. The problem is that the Main method being asynchronous, its ending may be executed on a different thread than the beginning. So, when I try to release the mutex, I get "Object synchronization method was called from an unsynchronized block of code" exception because the mutex cannot be released from another thread. But I don't use the mutex for inter-thread synchronyzation, I use it for inter-process synchronization. So I don't really care which thread releases the mutex since it's guaranteed that acquire and release are not conflicting with each other. I know there are two ways to avoid this error:

  • Use separate thread for mutex (this thread will acquire the mutex, then block and wait for application to exit, then release the mutex)
  • Use main thread with synchronization context, which will allow to run await continuations on the same thread

Both of these ways seem too cumbersome. Is there a better way to ensure single instance for async app?

Sergey Sokolov
  • 131
  • 1
  • 8
  • Does this answer your question? [What is a good pattern for using a Global Mutex in C#?](https://stackoverflow.com/questions/229565/what-is-a-good-pattern-for-using-a-global-mutex-in-c) – Trevor Dec 17 '20 at 15:12
  • Are you creating the mutex in a `using` statement? – asaf92 Dec 17 '20 at 15:14
  • 1
    Does this answer your question? [Named Mutex with await](https://stackoverflow.com/questions/23153155/named-mutex-with-await) – asaf92 Dec 17 '20 at 15:19
  • Have you tried the suggestion from [this](https://stackoverflow.com/questions/646480/is-using-a-mutex-to-prevent-multiple-instances-of-the-same-program-from-running/646500#646500 "Is using a Mutex to prevent multiple instances of the same program from running safe?") answer? It looks pretty nifty. They just create a named `EventWaitHandle`, and keep it alive for the duration of the app (a `GC.KeepAlive(handle)` at the end of the app may be needed). There is a comment warning for .NET Core not supporting this solution, but I just tested it on .NET 5 and it works fine. – Theodor Zoulias Dec 17 '20 at 18:19
  • @TheodorZoulias Yes, looks like it's much more convenient than mutex, thanks. Regarding .net core I believe it's about being non cross-platform. So when you use it under windows, it works, but if you try to run the same code for linux, for example, it will throw PlatformNotSupportedException. – Sergey Sokolov Dec 17 '20 at 22:54
  • Yeap, you are right, they are referring to other platforms. On Windows it should be OK. – Theodor Zoulias Dec 18 '20 at 01:07

2 Answers2

2

While you could use either of the approaches you mention, it would probably just be simpler to use a named semaphore instead of a named mutex.

Unlike mutexes, semaphores do not care which threads release them.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Looks like there is no reliable way to guarantee release of a named semaphore or to handle "abandoned" case like with mutexes. For example, if the app is killed, the next time it starts, it'll think that another instance is still running. – Sergey Sokolov Dec 17 '20 at 16:18
  • 1
    @ssokolov: Good point, and these behaviors are related. The entire concept of an "abandoned mutex" *exists* precisely *because* the release has to happen on the same thread. Semaphores don't care about threads; thus there is no concept of an "abandoned semaphore". If you need to handle abandoned primitives, then you'll have to stick with mutex and have a specific thread acquire/release it. – Stephen Cleary Dec 17 '20 at 16:57
  • 1
    What if I just remove release call altogether? Sure, next time I try to acquire, I get abandoned mutex exception, but it can be easily handled and it does not prevent from acquiring the mutex. I mean, could there be any undesirable side effects to this way? – Sergey Sokolov Dec 17 '20 at 22:35
  • @SergeySokolov: I can't think of any downsides to that. – Stephen Cleary Dec 18 '20 at 00:51
  • @SergeySokolov, the other answer about using named events probably suits your original usecase better, but just for completeness, the approach of never releasing the mutex would also come with problems, because if you acquire it on some random threadpool worker, you have no control over its lifetime. The thread may get released later as the threadpool rotates through threads while your program is still running. If you happen to acquire it on the original "Main Thread" (that is, before awaiting anything), it may work, as that thread blocks on Main's Task, but that is undocumented and may change – fbrosseau Dec 28 '20 at 02:51
2

You could use a synchronous Main entry point. For a console application, blocking the startup thread is not a big deal. Unlike GUI applications, this thread has nothing to do while asynchronous operations are carried out. This is what happens anyway when you use an async Task Main as entry point.

public static void Main(string[] args)
{
    var mutex = new Mutex(false, Assembly.GetEntryAssembly().GetName().Name);
    if (!mutex.WaitOne(0))
    {
        Console.WriteLine($"Already running. Press any key to continue . . .");
        Console.ReadKey();
        Environment.Exit(1);
    }
    try
    {
        MainAsync(args).GetAwaiter().GetResult();
    }
    finally
    {
        mutex.ReleaseMutex();
    }
}

public static async Task MainAsync(string[] args)
{
    // Main body here
}

Btw you should check out this question about a possible race condition, that could result to an AbandonedMutexException. I guess that the race condition is subtle, because I wasn't able to reproduce it.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Well, actually it's a little bit more complex than I described in the initial question. The mutex is released in async deinitialize method which is defined in another library. And this library is also used from WPF application, and no problem there because GUI thread uses synchronization context. So even if I block in Main, that method would still use await and its continuation would run on another thread. – Sergey Sokolov Dec 17 '20 at 22:10
  • 2
    @SergeySokolov I see. Then installing a suitable `SynchronizationContext` seems like the only option. You could use the `AsyncContext` from [this](https://www.nuget.org/packages/Nito.AsyncEx.Context/) package ([documentation](http://dotnetapis.com/pkg/Nito.AsyncEx.Context/5.0.0/netstandard2.0/doc/Nito.AsyncEx.AsyncContext)). You'll have to put all your code inside an `AsyncContext.Run(async () => { /* ... */ })` call. It's a blocking call, much like the analogous `Application.Run(new Form1());`. – Theodor Zoulias Dec 18 '20 at 01:19