我有两种方法,MethodA&方法b. MethodB必须在UI线程上运行.我需要它们一个接一个地运行,而不允许MethodC在它们之间运行.
当用户点击一个可爱的小按钮时,会调用MethodC.
我做了什么来确保这个代码锁定代码:
Lock (MyLock)
{
MethodA(param1, param2);
MyDelegate del = new MyDelegate(MethodB);
if (this.IsHandleCreated) this.Invoke(del);
}
对于MethodC:
public void MethodC()
Lock (MyLock)
{
Do bewildering stuff.....
}
问题是我卡住了.看起来我的代码陷入了僵局.
当我查看线程时,我看到按钮单击调用的代码卡在MethodC中的Lock(MyLock)中,而我的另一个线程似乎停留在此.Invoke(del).
我已经读过从Lock中调用一个方法很危险,但因为我是那个在那里编写代码的人,即使只是一个Thread.Sleep,这似乎也发生了.我认为这不是代码让我遇到麻烦.
为什么Invoked方法会停止工作?
是否可能在返回到从中调用的原始锁之前等待方法C上的锁被释放?
解决方法:
那么,想象一下以下情况:
>您的后台线程开始运行代码.它抓住锁,然后开始运行MethodA.
>当MethodA处于工作中时调用MethodC. MethodA等待锁是空闲的,阻止UI线程,直到发生这种情况.
>后台线程完成MethodA并在UI线程上调用MethodB.在消息泵队列中的所有先前项都已完成之前,MethodB无法运行.
> MethodC位于消息泵队列的顶部,等待MethodB完成,并且MethodB在队列中等待,直到MethodC完成.他们都在互相等待,这是一个僵局.
那么,你如何解决这个问题呢?你真正需要的是一些“等待”锁而不实际阻塞线程的方法.幸运的是(在.NET 4.5中),由于Task Parallel Library,这很容易实现. (我在引号中等待,因为我们实际上并不想等待,我们只想在释放锁定时立即执行MethodC而不实际等待/阻塞当前线程.)
而不是使用MyLock使用的对象:
private static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
现在,对于MethodC,您可以:
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();
}
}
这里的关键是因为我们等待一个表示信号量实际上是空闲的任务时我们没有阻塞当前线程,这将允许其他后台任务将MethodB封送到UI线程,完成方法,释放信号量,然后让这个方法执行.
您的其他代码不需要(但如果您愿意,仍然可以)在信号量上使用异步等待;阻塞后台线程几乎不是一个问题,因此唯一的关键更改是使用信号量而不是锁:
public void Bar()
{
try
{
semaphore.Wait();
MethodA(param1, param2);
MyDelegate del = new MyDelegate(MethodB);
if (this.IsHandleCreated) this.Invoke(del);
}
finally
{
semaphore.Release();
}
}