Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invoking Method on UI thread from within a Lock()

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?

like image 477
E.T. Avatar asked Feb 13 '13 18:02

E.T.


1 Answers

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();
    }
}
like image 90
Servy Avatar answered Nov 17 '22 05:11

Servy