0

I am trying to get the response from a url, and when I use the await and async in my function, my Mutex throws an error.

Error output :

System.ApplicationException
Object synchronization method was called from an unsynchronized block of code.
 at System.Threading.Mutex.ReleaseMutex()

Code :

private async void getData ()
{
    _mutex.WaitOne();

    try
    {
        string url = "https://urllink.com";
        HttpClient client = new HttpClient();
        string response = await client.GetStringAsync(url);
    }
    catch (Exception e)
    {
        // TODO
        throw e;
    }
           
    _mutex.ReleaseMutex();
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Blacky_99
  • 155
  • 4
  • 20
  • What makes you think you need a mutex, here? – Fildor Jul 27 '21 at 11:42
  • The mutex was implemented by a previous dev – Blacky_99 Jul 27 '21 at 11:43
  • 2
    Mhm. Why did he think you need a mutex here? – Fildor Jul 27 '21 at 11:43
  • @Fildor not sure either of those are relevant to the code, honestly; I can see uses for mutex here in terms of acting as a concurrency throttle – Marc Gravell Jul 27 '21 at 11:44
  • @MarcGravell That's why I am asking. I don't want to suggest throwing them out, if there's a good reason. – Fildor Jul 27 '21 at 11:45
  • In terms of *code*, there *is* a problem here in that the mutex isn't released in the failure case; the "release" should be in a `finally` so that it happens either way (and a `catch` that just has `throw e;` is a `catch` you can remove); however... I'm not sure that would fail *in the way discussed* – Marc Gravell Jul 27 '21 at 11:45
  • @MarcGravell Could synchronization context play into that exception, OP is seeing? – Fildor Jul 27 '21 at 11:47
  • 1
    You shouldn't use normal mutexes in async code. Async code using await can wander from thread to thread as await is handled, and mutexes have thread affinity, see [here](https://learn.microsoft.com/en-us/dotnet/standard/threading/mutexes) for more information. When a different thread than the one owning the mutex tries to release it, an ApplicationException is thrown, as you've experienced. – Lasse V. Karlsen Jul 27 '21 at 11:49
  • 1
    @Fildor normally you would *expect* this to break in the way that it is; ironically (perhaps), there are scenarios where sync-context might *silently fix* this code - for example, in winforms / WPF the sync-context would shunt the code back to the UI thread (assuming it started on the UI thread), which means you're back on the correct thread by a fluke – Marc Gravell Jul 27 '21 at 11:59
  • [Avoid async void](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#avoid-async-void). It is intended specifically for event handlers. To create asynchronous methods we use `async Task`. – Theodor Zoulias Jul 27 '21 at 15:15

1 Answers1

5

I would propose two three changes here:

  1. replace async void with async Task (credit: Fildor), and make sure you await it
  2. replace Mutex with SemaphoreSlim (a new SemaphoreSlim(1,1) is basically the same thing as a Mutex) - the Mutex documentation is heavily focused on "the thread that owns the mutex", which strongly suggests it is thread-bound, and await is incompatible with thread-bound scenarios; SemaphoreSlim, however, is not thread-bound; additionally, it has an async-aware WaitAsync() API, avoiding thread blocks (i.e. replace _mutex.WaitOne(); with await _semaphore.WaitAsync();)
  3. put the release in a finally, so that it is released even in the failure case

But "1" seems to be the real problem here. I would also speculate that this code worked fine until it was changed to async.

You could also remove the catch, as a catch that just has throw is redundant; a catch that just has throw e; is worse than redundant: it breaks the stack-trace.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Yes, the application exception is caused by the thread affinity of the mutex, a different thread tried to release the mutex than was currently owning it, this is explicitly not allowed. – Lasse V. Karlsen Jul 27 '21 at 11:50
  • 1
    Yes your speculation is indeed right that the code worked fine until I added async due to the await in client.GetStringAsync(url). The first change will be taken into consideration and second change was implemented but the issue still persists. – Blacky_99 Jul 27 '21 at 11:51
  • 1
    @Blacky_99 the first change is the thing that is needed - as confirmed by Lasse – Marc Gravell Jul 27 '21 at 11:52
  • 2
    You **cannot** use a Mutex in code that might transition to a different thread due to `await`. You **will** have this problem. You can keep most of the code mostly as-is, but you need to change to a synchronization object that doesn't have thread affinity. [AsyncEx by Stephen Cleary](https://github.com/StephenCleary/AsyncEx) has quite a few that can be used instead if you want the lock acquisition to be async as well. If that's not an issue you can switch to a SemaphoreSlim as Marc has described. – Lasse V. Karlsen Jul 27 '21 at 11:52
  • 2
    I'd also strongly recommend against `async void` in favor of `async Task`. And reuse of an HttpClient instance instead of creating a new one on each call. – Fildor Jul 27 '21 at 11:54
  • 1
    @Fildor great eyes - yeah, `async void` is very bad; added (with credit) – Marc Gravell Jul 27 '21 at 11:55
  • @Blacky_99 definitely that too ^^^^ - `async void`: nope every time – Marc Gravell Jul 27 '21 at 11:55
  • 1
    @LasseV.Karlsen "If that's not an issue" - either way: `SemaphoreSlim` *has* async acquisition, at least in any runtime that I wouldn't run screaming from – Marc Gravell Jul 27 '21 at 11:58
  • 1
    Ah, OK, then I missed the async acquisition :) – Lasse V. Karlsen Jul 27 '21 at 11:59