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

时间:2013-02-13 18:14:08

标签: c# multithreading locking invoke

我有两种方法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)的{​​{1}},我的另一个主题似乎停留在MethodC

我已经读过在this.Invoke(del)内调用方法很危险但是因为我是那个在那里编写代码的人,即使只有一个Lock,这似乎也发生了。不是让我陷入麻烦的代码。

为什么Invoked方法会停止工作? 它是否可能等待Thread.Sleep上的锁被释放,然后再回到它调用的原始锁?

4 个答案:

答案 0 :(得分:7)

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

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

  2. MethodC处于工作中时,会调用
  3. MethodAMethodA等待锁是免费​​的,阻止UI线程,直到发生这种情况。

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

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

  6. 那么,你如何解决这个问题呢?你真正需要的是一些“等待”锁而不实际阻塞线程的方法。幸运的是(在.NET 4.5中),由于Task Parallel Library,这很容易实现。 (我等待引号,因为我们实际上并不想等待,我们只想在释放锁定后立即执行MethodC ,而实际等待/阻止当前线程。)

    而不是使用objectMyLock使用:

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

答案 1 :(得分:-1)

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

lock(MyLock) {

}

你是完全正确的,你已经死锁了。 MethodA无法锁定MyLock,因为在输入方法之前它已被锁定。

答案 2 :(得分:-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);
 }

答案 3 :(得分:-1)

你混淆了使用锁的人。此任务与多线程本身无关。

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