从 Lock() 中调用 UI 线程上的方法

本文关键字:线程 方法 UI 调用 Lock | 更新日期: 2023-09-27 17:59:03

我有两个方法,MethodAMethodBMethodB必须在 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.....
 }

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

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

我已经读过从Lock内调用方法是危险的,但由于我是在那里编写代码的人,即使只有Thread.Sleep,这似乎也会发生,我认为这不是代码让我陷入麻烦。

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

从 Lock() 中调用 UI 线程上的方法

因此,想象一下以下情况:

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

  2. MethodC是在MethodA工作时调用的。 MethodA等待锁释放,阻止 UI 线程,直到发生这种情况。

  3. 后台线程完成MethodA,并在 UI 线程上调用MethodB。 在消息泵队列中的所有先前项目完成之前,MethodB无法运行。

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

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

而不是使用object 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();
    }
}

这里的关键是,因为我们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();
    }
}

假设你的MethodA也包含如下内容:

lock(MyLock) {
}

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

你可以试试这个:

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

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

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