【问题标题】:Invoking Method on UI thread from within a Lock()从 Lock() 中调用 UI 线程上的方法
【发布时间】:2013-02-13 18:14:08
【问题描述】:

我有两种方法,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 似乎也会发生这种情况,我认为这不是代码给我惹麻烦了。

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

【问题讨论】:

  • 方法 C 正在等待方法 B 获得的锁,方法 C 无法运行 b4 方法 B。

标签: c# multithreading locking invoke


【解决方案1】:

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

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

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

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

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

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

不要使用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();
    }
}

【讨论】:

  • 正是这种情况! Mu 的问题是,如果MethodB 与 A 在同一个锁中,为什么不能运行?为什么要“重新进入”?
  • @E.T.它根本不等待锁。它只是等待由 Windows 消息泵安排。它正在等待MethodC 完成执行,以便UI 线程可以继续执行消息泵中的其他消息,例如要运行的消息MethodB。此外,请参阅添加解决方案的编辑。
  • 谢谢。调查它:)
  • C# 4.0 有解决方案吗?
  • @gartenriese 请参阅this series of blog posts,了解为异步世界创建各种线程原语。
【解决方案2】:

假设您的 MethodA 还包含类似的内容:

lock(MyLock) {

}

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

【讨论】:

  • 如果它在同一个线程上就好了——锁是可重入的。问题是如果MethodB 试图获得锁......
  • MethodA 不锁定任何东西。所有锁都被引用。我不明白为什么MethodB 甚至试图获得锁:-/
【解决方案3】:

你可以试试这个:

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 并不总是在MethodB 之后运行。有时它从不运行,有时它运行之前,或两次,或前后运行。它们是独立的操作,它们不能同时运行,因为它们都需要某种共享资源。
【解决方案4】:

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

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

【讨论】:

  • 谢谢你,但整个事情至少每秒调用一次。是的,这也是让我感到困惑的原因......
  • 在这种情况下,您需要将用户交互排队并在队列中有内容时调用 MethodC。无论如何,您似乎需要更详细地解释逻辑(仍然感觉您完全走错了方向)
  • 好的,我认为 Servy 给了你正确的解决方案(如果你确定你的 A 和 B 将在 C 之前运行,那么你需要一个信号量)。但请记住,如果错误或意外情况不会调用其中一种方法并且您的代码将永远挂起,您很容易失去控制……我将始终选择更易于管理的代码(不能说更多,因为没有足够的信息调用这些方法实际需要什么)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-15
  • 2012-01-24
  • 1970-01-01
  • 1970-01-01
  • 2019-04-29
相关资源
最近更新 更多