5

我有两种方法,MethodA& MethodBMethodB必须在 UI 线程上运行。我需要它们一个接一个地运行,而不允许MethodC在它们之间运行。

MethodC当用户点击一个可爱的小按钮时调用。

我所做的以确保将其放在Lock代码周围:

 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.....
 }

问题是我被卡住了。看起来我的代码陷入了僵局。

当我查看线程时,我看到按钮单击调用的代码卡在lock (MyLock)了,MethodC而我的另一个线程似乎卡在了this.Invoke(del).

我已经读过从 a 中调用方法很危险,Lock但是因为我是在那里编写代码的人,即使只使用 a 似乎也会发生这种情况,Thread.Sleep我认为这不是让我陷入困境的代码。

为什么 Invoked 方法会停止工作?methodC在返回调用它的原始锁之前是否可能等待释放锁?

4

4 回答 4

10

所以,想象一下下面的情况:

  1. 您的后台线程开始运行代码。它抓住锁,然后开始运行MethodA

  2. MethodCMethodA在其工作中 被调用。MethodA等待锁被释放,阻塞 UI 线程,直到发生这种情况。

  3. 后台线程完成并在 UI 线程上 MethodA调用。直到消息泵队列中的所有先前项目都完成后才能运行。MethodBMethodB

  4. MethodC位于消息泵队列的顶部,等待MethodB完成,并MethodB在队列中等待MethodC完成。他们都在互相等待,这是一个僵局。

那么,你如何解决这个问题呢?您真正需要的是某种“等待”锁而不实际阻塞线程的方式。幸运的是(在 .NET 4.5 中)这很容易做到,这要归功于任务并行库。(我在引号中等待,因为我们实际上并不想等待,我们只想MethodC在锁被释放后立即执行,而无需实际等待/阻塞当前线程。)

而不是使用objectforMyLock使用:

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();
    }
}

这里的关键是,因为我们await的任务表示信号量何时真正空闲,所以我们不会阻塞当前线程,这将允许其他后台任务编组MethodB到 UI 线程,完成方法,释放信号量,然后让这个方法执行。

您的其他代码不需要(但如果您愿意,仍然可以)在信号量上使用异步等待;阻塞后台线程几乎不是什么大问题,所以唯一的关键变化是使用信号量而不是lock

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

        MyDelegate del = new MyDelegate(MethodB);
        if (this.IsHandleCreated) this.Invoke(del);
    }
    finally
    {
        semaphore.Release();
    }
}
于 2013-02-13T18:20:19.627 回答
-1

假设您MethodA还包含以下内容:

lock(MyLock) {

}

你是绝对正确的,你有一个死锁。 MethodA无法获得锁定,MyLock因为在输入方法之前它已经被锁定。

于 2013-02-13T18:18:23.590 回答
-1

你可以试试这个:

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);
 }
于 2013-02-13T18:28:09.237 回答
-1

你把使用锁的人搞糊涂了。该任务与多线程本身无关。

简单的可用性解决方案是您所需要的——禁用您可爱的小按钮,直到您准备好运行 MethodC。

于 2013-02-14T03:14:55.640 回答