23

Basically, I want to make multiple asynchronous requests to a tcp Server. I currently have a working client that is only synchronous and blocks the UI on every network call. Since multiple requests might occur at almost the same time, I tried to do this:

private object readonly readLock = new object(); 
public async Task UpdateDetailsAsync()
{
    //I want every request to wait their turn before requesting (using the connection) 
    //to prevent a read call from catching any data from another request
    lock (readLock)
    {
        Details details = await connection.GetDetailsAsync();
        detailsListBox.Items = details;
    }
}

I am sure this is not a good use of lock but it's the only way I can think of that could make the calls wait for their turn. Is there an object I can use to achieve this kind of behavior? I thought Monitor would be the same so I didn't try (I understand they're multithreading stuff but that's all I'm familiar with...)

Philippe Paré
  • 3,813
  • 5
  • 27
  • 50
  • one way to do it would be to put your calls into a queue. lock on adding actions to the queue, not on the operations themselves. have the queue complete in the background. – Cory Nelson Aug 19 '15 at 01:20

2 Answers2

59

Looks like the problem that you have is that threads will block while acquiring the lock, so your method is not completely async. To solve this you can use SemaphoreSlim.WaitAsync

private readonly SemaphoreSlim readLock = new SemaphoreSlim(1, 1); 
public async Task UpdateDetailsAsync()
{
    //I want every request to wait their turn before requesting (using the connection) 
    //to prevent a read call from catching any data from another request
    await readLock.WaitAsync();
    try
    {
        Details details = await connection.GetDetailsAsync();
        detailsListBox.Items = details;
    }
    finally
    {
        readLock.Release();
    }
}
Jared Moore
  • 3,425
  • 22
  • 31
  • If I understand semaphores correctly, the right way to instanciate in my case is `new SemaphoreSlim(0, 1)`, if I only want one at a time right? – Philippe Paré Aug 19 '15 at 01:33
  • Yes that's right. Thanks for the correction. Edited my answer. – Jared Moore Aug 19 '15 at 03:43
  • 2
    Minor correction again, I implemented all of that, it works perfectly. the only thing, the iintial count has to be `1`, not `0`. every time we call WaitAsync() and it returns, the count decreases. Thanks! – Philippe Paré Aug 19 '15 at 05:13
  • Looking back at this solution after a year, I giggle when I see `await SemaphoreSlim.WaitAsync()`. – Philippe Paré Sep 06 '16 at 14:58
  • 3
    why do you giggle? – DasAmigo Oct 13 '16 at 16:17
  • 3
    The `await readLock.WaitAsync();` statement should come before the `try` block. If `WaitAsync` threw an exception, you would be calling `Release()` without ever acquiring a lock. This could possibly trigger a `SemaphoreFullException`. If the `SemaphoreSlim` allowed limited multiple concurrent requests, you may also be allowing more than intended. – Brad M Sep 28 '18 at 19:51
  • 1
    Edit your answer, so await of lock will be before Try. – eocron Feb 13 '19 at 16:32
  • i did some test, and found this way correctly sync all threads, but it make the run time significantly longer, demo code in [github](https://github.com/LeiYangGH/async-lock-test/blob/master/asynclock/Program.cs) – Lei Yang Sep 15 '20 at 08:11
18

This problem has been neatly solved by the NuGet package Nito.AsyncEx which has over 50,000 downloads as of August 2015.

From ReadMe:

#AsyncEx A helper library for async/await.#

Supports .NET 4.5/4.0, iOS, Android, Windows Store 8.0, Windows Phone Silverlight 8.0/7.5, Windows Phone Applications 8.1, Silverlight 5.0/4.0, and all portable libraries thereof.

[snip]

#AsyncLock#

A lot of developers start using this library for AsyncLock, an async-compatible mutual exclusion mechanism. Using AsyncLock is straightforward:

private readonly AsyncLock _mutex = new AsyncLock();
public async Task UseLockAsync()
{
  // AsyncLock can be locked asynchronously
  using (await _mutex.LockAsync())
  {
    // It's safe to await while the lock is held
    await Task.Delay(TimeSpan.FromSeconds(1));
  }
}

See the C# source code on GitHub or install the NuGet package Nito.AsyncEx.

Pang
  • 8,605
  • 144
  • 77
  • 113
Contango
  • 65,385
  • 53
  • 229
  • 279
  • is there any way to let the code run in parallel, and also synced? i have a demo that shows after using `AsyncLock`, the total time used is obviously slower, compared to async one. [github](https://github.com/LeiYangGH/async-lock-test/blob/AsyncLockLib/asynclock/Program.cs) – Lei Yang Sep 15 '20 at 08:18