68

I am not clear (and can't find documentation clear enough): when using the lock keyword in an async method: will the thread be blocked if the object is already blocked or will it return a task in suspended state (not blocking the thread, and returning when the lock is released)?

In the code below, will the line block the thread? If it blocks the thread (which is what I think), is there an standard not blocking solution? I am considering using AsyncLock, but first I wanted to try for something standard.

private object myLock = new object(); 

private async Task MyMethod1()
{
    lock (myLock) // <---- will this line cause a return of the current method
                  // as an Await method call would do if myLock was already locked? 
    {
        //.... 
    }
}

// other methods that lock on myLock
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
rufo
  • 5,158
  • 2
  • 36
  • 47
  • 3
    Take a look here for a good explanation http://stackoverflow.com/questions/7612602/why-cant-i-use-the-await-operator-within-the-body-of-a-lock-statement – atomaras Nov 19 '13 at 23:56
  • Does this answer your question? [Why can't I use the 'await' operator within the body of a lock statement?](https://stackoverflow.com/questions/7612602/why-cant-i-use-the-await-operator-within-the-body-of-a-lock-statement) – Cole Tobin Jun 08 '23 at 18:54

4 Answers4

101

This has been disallowed to stop deadlocks (i.e. developers hurting themselves). The best solution I've found is to use semaphores - See this post for details.

Relevant code extract:

static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);

...

await semaphoreSlim.WaitAsync();
try
{
    await Task.Delay(1000);
}
finally
{
    semaphoreSlim.Release();
}
CarenRose
  • 1,266
  • 1
  • 12
  • 24
user851220
  • 1,071
  • 2
  • 7
  • 7
  • 2
    Is there a reason for making the semaphore static? presumably it would work just as well if the semaphore were an instance variable (assuming you wanted to only lock this instance of the class) – Andy Jun 02 '20 at 12:34
  • 2
    In my case, I have multiple objects that need to report their progress to a database. I want them to be async of course, so a static SemaphoreSlim in my case is the way to go. – Mladen Ristic Apr 13 '21 at 08:50
  • Thanks for the answer. The use of SemaphoreSlim, solved my problem. – Silvair L. Soares Apr 04 '23 at 18:19
  • Regarding Static: on a webserver, if you have an singleton that uses async... Each Request against the server is usually executed in its own thread - so if you have 3-4 almost concurrent calls they might try accessing the object simultaniously. – Martin Kirk May 02 '23 at 10:31
  • I forgot to say, that the only way to share a semaphore across threads on a webserver, is to use a Static Semaphore. – Martin Kirk May 02 '23 at 10:46
82

In the code below, will the line block the thread?

Technically, yes, but it won't work as you expect.

There are two reasons why thread-affine locks don't play well with async. One is that (in the general case), an async method may not resume on the same thread, so it would try to release a lock it doesn't own while the other thread holds the lock forever. The other reason is that during an await while holding a lock, arbitrary code may execute while the lock is held.

For this reason, the compiler goes out of its way to disallow await expressions within lock blocks. You can still shoot yourself in the foot by using Monitor or other primitives directly, though.

If it blocks the thread (which is what I think), is there an standard not blocking solution?

Yes; the SemaphoreSlim type supports WaitAsync.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 5
    Thanks now that I see the problem doing `await` in `lock`. What about the opposite: using `lock`s in a method that's called eventually by some async method? My guess is that it's safe, as the `lock`ed operation are all completed on the same thread. However, using Monitors directly seems still problematic in this case - but that sucks, because there is usually no way the dev can tell if the code is going to be used in a async/await fashion or not. What's the suggestion? – KFL Apr 05 '17 at 00:18
  • 8
    As long as there's only synchronous code in the lock (or monitor), then it'll work fine. It doesn't matter whether it's called by an asynchronous method or not. – Stephen Cleary Apr 05 '17 at 00:21
  • 4
    I thought this is what the question was asking (Is a lock inside an async method safe). Thankfully I realized it was a little unclear and scrolled down to these comments. Hahaha. –  Mar 27 '20 at 22:24
  • *"why thread-affine locks don't play well with async"* - I can't wrap my head around that: shouldn't the following always happen? (1) thread 1 reaches lock, acquires it (2) thread 1 reaches await, let go of execution (3) thread 2 reaches lock, blocks on it until released (that's bad in an async function, but that's another issue) (4) thread 1 execution continues (awaiting finished), releases lock, happily continues (5) thread 2 acquires lock, happily continues. – OfirD Jun 17 '21 at 23:41
  • 2
    @OfirD: There's one major problem: the `async` method may resume executing on a *different* thread after the `await`, leading to a situation where a thread releases a lock it never acquired. – Stephen Cleary Jun 18 '21 at 00:33
  • Huh, that's already in your answer, but only now I got it :) But what about the other reason you give: *"during an await while holding a lock, arbitrary code may execute while the lock is held*" - continuing my last example, I'd say that thread 2 is the "arbitrary code [being] execute[d] while the lock is held" by thread 1. What can go wrong with that? – OfirD Jun 18 '21 at 06:31
  • 1
    @OfirD: Now consider multiple async methods run on the *same* thread 1: function A acquires the lock and does the `await`. Then function B runs on the same thread; if it acquires the same lock, it will succeed instead of blocking. Also, in the more general case, function B can depend on some code run by thread 2 that acquires the lock - in that case you end up with a deadlock because thread 1 is holding the lock, waiting for thread 2, which is waiting for thread 1 to release the lock. – Stephen Cleary Jun 18 '21 at 12:12
  • Thanks so much! I'll drop here a link related to the first part of your last comment: [Under what conditions can a thread enter a lock (Monitor) region more than once concurrently?](https://stackoverflow.com/questions/13983753/under-what-conditions-can-a-thread-enter-a-lock-monitor-region-more-than-once) – OfirD Jun 18 '21 at 13:50
  • What's the problem with "arbitrary code executing while the lock is held"? I've seen this in multiple places now and I really wish someone would give a full for-dummies explanation with a worked example because I'm not seeing the problem. – Jez Jan 29 '23 at 12:53
  • It's one of three multithreading pillars from an old Dr Dobbs article IIRC. In the general case, executing arbitrary code while holding a lock may cause a deadlock, e.g., if the arbitrary code chooses to depend on some other thread taking that same lock. – Stephen Cleary Jan 30 '23 at 10:39
19

No it won't.

lock is syntactic sugar for Monitor.Enter and Monitor.Exit. lock will keep execution in the method until the lock is released. It does not function like await in any way, shape or form.

Simon Whitehead
  • 63,300
  • 9
  • 114
  • 138
  • 1
    Thanks. BTW: I guess applying to my question: "Yes, the thread will block", is the answer that I am looking for. – rufo Nov 19 '13 at 23:56
  • 1
    lock() spins for a short period and then blocks. If it just kept spinning it'd change inefficient code into inefficient time-and-power-eating code. – Cory Nelson Nov 20 '13 at 00:47
  • A bad use of the word "spin" on my part. I just meant to say that execution does not leave the method like it does with `await`. I will rephrase. Thanks @CoryNelson. – Simon Whitehead Nov 20 '13 at 00:56
2

You task will not return in suspended state. It will wait until myLock is unlocked to run the code within lock statement. It will happen no matter what C# asynchronous model you use.

In other words, no two threads will be able to run statements inside the lock. Unless, there are many different instances of myLock object.

evhen14
  • 1,839
  • 12
  • 16