1

i have some code as belows:

static void Main( string[] args )
    {
        var async = new object();

        Monitor.Enter( async );

        Task.Run( async () =>
        {
            await Task.Delay( 5000 );
            Monitor.Exit( async );
        } );

        Monitor.Wait( async );
        Monitor.Exit( async );
    }

but it will block at Monitor.Wait forever. i can solve this problem by use semaphore.but i want to know why it happens and what about mutex,spinlock,barrier.thks!

HongyanShen
  • 1,435
  • 2
  • 14
  • 23
  • 3
    "but it will block at Monitor.Wait forever" - well yes, nothing's calling `Monitor.Pulse`. It's not clear why you expected this to work, but calling `Monitor.Exit` on a thread that doesn't own the monitor will throw an exception. I'd also *strongly* recommend against naming a variable `async`, especially in a method which is also using async/await. – Jon Skeet Nov 05 '16 at 10:07
  • what you exactly trying to do ? – Vivek Nuna Nov 05 '16 at 10:14
  • http://stackoverflow.com/a/21404261/6527049 – Vivek Nuna Nov 05 '16 at 10:16
  • @viveknuna just for testing. – HongyanShen Nov 06 '16 at 03:46

1 Answers1

3

Monitor has thread-affinity. You can't exit it from a different thread.

But it's not clear what you're trying to do here anyway. You're already in an async method, calling another async method. Just use await - just setup an synchronization context to allow you to use await properly in a console application. Or, use Task.Wait if you're not worried about deadlocks :)

As for Monitor.Wait, it doesn't do what you think it does. The wait is for a signal, not for the monitor being exited. For example:

static readonly object syncObject = new object();

void Thread1()
{
  lock (syncObject) Monitor.Wait(syncObject);
}

void Thread2()
{
  lock (syncObject) Monitor.Pulse(syncObject);
}

Both methods are executed on separate threads in this scenario. If Thread1 runs first, it will take the lock, wait for a signal (this exits the lock until the signal is presented) and reacquire the lock after the signal is given. Thread1 provides the signal using the Monitor.Pulse method. Note that in both cases, the lock is taken on a single thread, and exited on the same thread.

This mechanism isn't very easy to use properly, and is somewhat slow, so you're not going to see it much.

Also, the threads used by Task.Run aren't yours. It's usually undesirable to use blocking code on thread-pool threads - make sure you understand the trade-offs your making. Even more importantly, tasks have no thread-affinity - so using a thread-affine synchronization primitive like a monitor is rather adventurous :) In your case, even if you took the lock inside the Task.Run rather than the outside, it would be possible for the Monitor.Exit to fail, since you might have gotten a different thread after the await.

Don't forget that the trickiest part about multi-threading isn't that it just doesn't work - it's that it has an ugly tendency to mostly work for the most part, and fail in weird scenarios (that happen all the time in practice, mind you). Testing isn't quite enough to give you much confidence in how a multi-threaded piece of code behaves in practice. All you get is tons of tiny, almost impossible to reproduce bugs. You got lucky here - your code fails reliably; that certainly isn't the case with most multi-threading issues :)

Joe Albahari's introduction to threading is a huge help for multi-threaded programming of any kind. I highly recommend reading through the whole of it at least once. Keep it in your bookmarks for future reference :)

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • "`Monitor` has thread affinity"...I knew this to be true but it took quite a while to find the official docs on it. For anyone else who is curious here is [the official link](https://learn.microsoft.com/en-us/dotnet/standard/threading/overview-of-synchronization-primitives#monitor-class) (see last sentence of the first paragraph in the "Monitor class" section). – Eric Mutta May 13 '23 at 15:58