5

I have two methods, MethodA & MethodB. MethodB has to run on the UI thread. I need them to run one after the other without allowing MethodC to run between them.

MethodC is called when a user clicks on a lovely little button.

What I did to ensure this is put a Lock around the code thus:

 lock (MyLock)
 {
   MethodA(param1, param2);

   MyDelegate del = new MyDelegate(MethodB);
   if (this.IsHandleCreated) this.Invoke(del);
 }

And for MethodC:

public void MethodC()
 lock (MyLock)
 {
   Do bewildering stuff.....
 }

Problem is I'm getting stuck. It looks like my code's going into a deadlock.

When I look at the threads I see that the code called by the button click is stuck at lock (MyLock) in MethodC and my other thread seems stuck at this.Invoke(del).

I've read it's dangerous to invoke a method from within a Lock but since I'm the one who wrote the code there and this seems to happen even with just a Thread.Sleep I figure it's not the code that's getting me into trouble.

Why would the the Invoked method stop working? Is it possibly waiting for the lock on methodC to be released before going back to the original lock it was invoked from?

weshouman
  • 632
  • 9
  • 17
E.T.
  • 949
  • 8
  • 21
  • Method C is waiting on a the lock which Method B has acquired and method C cant run b4 method B. – Igoy Feb 13 '13 at 18:24

4 Answers4

10

So, imagine the following situation:

  1. Your background thread starts running the code. It grabs the lock and then starts running MethodA.

  2. MethodC is called while MethodA is in the middle of its work. MethodA waits for the lock to be free, blocking the UI thread until that happens.

  3. The background thread finishes MethodA and goes to invoke MethodB on the UI thread. MethodB can't run until all previous items in the message pump's queue have finished.

  4. MethodC is at the top of the message pump's queue, waiting until MethodB finishes, and MethodB is in the queue waiting until MethodC finishes. They are both waiting on each other, which is a deadlock.

So, how do you resolve this issue? What you really need is some way of "waiting" on a lock without actually blocking the thread. Fortunately (in .NET 4.5) this is easy to do thanks to the Task Parallel Library. (I have waiting in quotes because we don't actually want to wait, we just want to execute MethodC as soon as the lock is freed without actually waiting/blocking the current thread.)

Instead of using an object for MyLock use:

private static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);

Now for MethodC you can do:

public async Task MethodC() //you can change the signature to return `void` if this is an event handler
{
    try
    {
        await semaphore.WaitAsync();
        //Do stuff
    }
    finally
    {
        semaphore.Release();
    }
}

The key here is that because we await a task that represents when the semaphore is actually free we aren't blocking the current thread, which will allow the other background task to marshal MethodB to the UI thread, finish the method, release the semaphore, and then let this method execute.

Your other code doesn't need to (but still can, if you want) use async waiting on the semaphore; blocking the background thread isn't nearly as much of a problem, so the only key change there is using the semaphore instead of lock:

public void Bar()
{
    try
    {
        semaphore.Wait();
        MethodA(param1, param2);

        MyDelegate del = new MyDelegate(MethodB);
        if (this.IsHandleCreated) this.Invoke(del);
    }
    finally
    {
        semaphore.Release();
    }
}
Servy
  • 202,030
  • 26
  • 332
  • 449
  • Precisely the situation! Mu question is why can't `MethodB` run if it's in the same Lock as A? Why is it trying to "re-enter"? – E.T. Feb 13 '13 at 18:23
  • @E.T. It's not waiting on the lock at all. It's just waiting to be scheduled by the windows message pump. What it's waiting on is for `MethodC` to finish executing so that the UI thread can continue on executing other messages in the message pump, such as the message to run `MethodB`. Also, see edit with solution added. – Servy Feb 13 '13 at 18:31
  • Thanks. Looking into it :) – E.T. Feb 13 '13 at 18:39
  • 1
    Is there a solution for C# 4.0? – gartenriese Jun 16 '16 at 12:33
  • @gartenriese See [this series of blog posts](http://blogs.msdn.com/b/pfxteam/archive/2012/02/11/10266920.aspx) on creating various threading primitives for an async world. – Servy Jun 16 '16 at 12:50
  • They use FromResult, which is not available in C# 4.0. – gartenriese Jun 16 '16 at 13:05
  • @gartenriese It's trivial enough to write your own: http://stackoverflow.com/questions/15562845/is-returning-an-empty-static-task-in-tpl-a-bad-practice/15571885#15571885 – Servy Jun 16 '16 at 13:10
  • Yeah sorry, figured that out right after I asked. In your answer above, you are using async and await. What should I use instead? – gartenriese Jun 16 '16 at 14:10
  • @gartenriese `ContinueWith`. – Servy Jun 16 '16 at 14:21
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/114852/discussion-between-gartenriese-and-servy). – gartenriese Jun 16 '16 at 14:50
  • @gartenriese: `await` can work perfectly fine in .Net 4.0. – SLaks Jun 16 '16 at 14:55
  • @SLaks: I am trying to avoid any additional libraries. – gartenriese Jun 16 '16 at 15:00
  • @gartenriese: **Don't**. Async is not simple; if you try to reinvent everything yourself, you'll be in for a world of pain. – SLaks Jun 16 '16 at 15:01
  • @SLaks: It's not my decision, unfortunately. If it was, I'd be using .NET 4.6.1. – gartenriese Jun 16 '16 at 15:24
-1

Assuming your MethodA also contains something like:

lock(MyLock) {

}

You are absolutely correct, you've got a dead lock. MethodA cannot obtain a lock on MyLock because it was already locked before the method was entered.

CodingGorilla
  • 19,612
  • 4
  • 45
  • 65
  • 2
    It would be fine if it was on the same thread - locks are re-entrant. What's problematic is if `MethodB` tries to obtain the lock... – Jon Skeet Feb 13 '13 at 18:19
  • `MethodA` doesn't lock anything. All locks are quoted. What I can't figure out is why `MethodB` is even trying to obtain the lock :-/ – E.T. Feb 13 '13 at 18:26
-1

You can rather try this:

Lock (MyLock)
 {
   MethodA(param1, param2);

   MyDelegate del = new MyDelegate(MethodB);
   MyDelegate del2 = new MyDelegate(MethodC);
   MyDelegate del3 = del+del2
   if (this.IsHandleCreated) this.Invoke(del3);
 }
Igoy
  • 2,942
  • 22
  • 23
  • But `MethodC` isn't always run right after `MethodB`. Sometimes it's never run, sometimes it's run before, or twice, or before and after. They're independent operations, they just can't run at the same time since they both require a shared resource of some sort. – Servy Feb 13 '13 at 18:29
-1

You confused a hell out of people using lock. This task has nothing to do with multi-threading per se.

Simple usability solution is what you need - disable your lovely little button until you are ready to run MethodC.

Boppity Bop
  • 9,613
  • 13
  • 72
  • 151
  • Thanks for that but this whole thing is called up at least once a second. And yeah, that's what got me confused too... – E.T. Feb 14 '13 at 08:30
  • in that case you need to queue the user interactions and call MethodC if there is something in the queue. anyway it seems you need to explain logic in more details (it still feels like you are going in a wrong direction altogether) – Boppity Bop Feb 14 '13 at 12:39
  • OK I think Servy gave you right solution (if you know for sure that your A and B will run before C then you need a semaphore). but bear in mind you can lose control easily if a bug or unexpected situation will not call one of the methods and your code will hung forever... I will always opt for more manageable code (cant say more because there is not enough information on what you actually need to achieve calling these methods) – Boppity Bop Feb 14 '13 at 12:45