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?
So, imagine the following situation:
Your background thread starts running the code. It grabs the lock and then starts running MethodA
.
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.
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.
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();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With