【问题标题】:WPF Dispatcher.Invoke 'hanging'WPF Dispatcher.Invoke 'hanging'
【发布时间】:2008-11-05 02:01:54
【问题描述】:

我有一个有点复杂的 WPF 应用程序,在尝试使用调度程序调用 UI 线程上的调用时,它似乎“挂起”或卡在等待调用中。

一般流程是:

  1. 处理按钮的点击事件
  2. 创建一个新线程 (STA):创建演示者和 UI 的新实例,然后调用方法 Disconnect
  3. Disconnect 然后在 UI 上设置一个名为 Name 的属性
  4. Name 的设置器然后使用以下代码设置属性:

    if(this.Dispatcher.Thread != Thread.CurrentThread)
    {
        this.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate{
            this.Name = value; // Call same setter, but on the UI thread
        });
        return;
    }

    SetValue(nameProperty, value); // I have also tried a member variable and setting the textbox.text property directly.

我的问题是,当调用调度程序 invoke 方法时,它似乎每次都挂起,并且调用堆栈表明它处于睡眠状态,等待或加入 Invoke 实现。

那么,我是否做错了什么我遗漏了,明显与否,或者是否有更好的方法来调用 UI 线程来设置此属性(和其他属性)?

编辑: 解决方案是在线程委托结束时调用 System.Windows.Threading.Dispatcher.Run()(例如,正在执行工作的位置) - 感谢所有提供帮助的人.

【问题讨论】:

  • @Matthew - 实际上,BeginInvoke 没有什么“非最佳”的;如果您现在绝对不需要更新,那很好。不过,您需要对捕获的变量稍加小心(即,在调用 BeginInvoke 后不要更改“值”。完全如此。)
  • @Matthew - 你根本不加入()新线程,是吗?这可以解释它......
  • @Marc Gravell - 从记忆中我在某个时候加入了线程,但我不确定当我不使用它时行为是否相同。加入的原因是我想在工作完成之前阻止应用程序的其余部分,但也许我可以使用其他方法。

标签: c# .net wpf invoke dispatcher


【解决方案1】:

Invoke 是同步的 - 您需要 Dispatcher.BeginInvoke。另外,我相信您的代码示例应该将“SetValue”移动到“else”语句中。

【讨论】:

    【解决方案2】:

    我认为用代码更好地显示这一点。考虑这种情况:

    线程 A 这样做:

    lock (someObject)
    {
       // Do one thing.
       someDispatcher.Invoke(() =>
       {
          // Do something else.
       }
    }
    

    线程 B 这样做:

    someDispatcher.Invoke(() =>
    {
       lock (someObject)
       {
          // Do something.
       }
    }
    

    乍一看,一切可能看起来都很好,但事实并非如此。这会产生死锁。调度程序就像线程的队列,在处理此类死锁时,以这样的方式思考它们很重要:“以前的调度可能会阻塞我的队列吗?”。线程 A 将进入......并在锁下调度。但是,如果线程 B 在线程 A 在代码中标记为“做一件事”的时间点出现怎么办?嗯……

    • 线程 A 已锁定 someObject 并正在运行一些代码。
    • 线程 B 现在进行调度,调度程序将尝试获取 someObject 上的锁,由于线程 A 已经拥有该锁,因此阻塞了您的调度程序。
    • 然后线程 A 将排队另一个调度项。这个项目永远不会被解雇,因为你的调度员永远不会完成你之前的请求;它已经卡住了。

    你现在有一个漂亮的僵局。

    【讨论】:

    • 感谢您的精彩解释。节省了我的工作时间。我通过不在调度程序调用中获取锁来修复它(你的线程 B 所做的)。这个问题有其他解决方案吗?
    • @Heribert 这取决于您正在使用的代码。像这样的死锁是非常特定于应用程序的。如果您遇到与上述类似的情况,您可以尝试在调度程序调用之外进行锁定。
    • 这就是我所做的 :) 再次感谢您的帖子和回复
    【解决方案3】:

    你说你正在创建一个新的 STA 线程,这个新线程上的调度程序是否正在运行?

    我从“this.Dispatcher.Thread != Thread.CurrentThread”中得知您希望它是一个不同的调度程序。确保它正在运行,否则它不会处理它的队列。

    【讨论】:

    • 基思,这是一个很好的观点。我对dispatcher不够熟悉,但是window的dispatcher不是已经启动了吗? STA 线程用于创建新窗口,但是如果我需要手动启动调度程序,它将解释为什么它没有处理......
    • 如果您自己创建 STA,请在显示窗口后尝试调用 Dispatcher.Run()。我的理解是调度程序是一个消息泵,如果创建一个新的 UI 线程,它将在请求时创建一个调度程序,如果您管理创建,则必须在调度程序上调用 Run。
    【解决方案4】:

    我想你的意思是 if (!this.Dispatcher.CheckAccess())

    我也遇到了 Invoke 的问题,或者如果我可以 BeginInvoke,我的代表没有被调用 - 似乎在做所有事情 :-(

    【讨论】:

      【解决方案5】:

      这听起来像是一个僵局;如果调用 .Invoke 的线程已经持有 UI 线程完成其工作所需的锁 / 互斥锁 / 等,这通常会发生。最简单的方法是改用 BeginInvoke:这样,当前线程可以继续运行,并且(可能)很快就会释放锁 - 允许 UI 获取它。或者,如果您能识别出有问题的锁,您可以故意将其释放一段时间。

      【讨论】:

      • 感谢 Marc,这个解释很好,但是我仍然不知道为什么首先会有锁。正如您自己和 Paul BeginInvoke 所建议的那样,它是一个选项,但不是最优的,不能保证它会完成。疯狂的奇怪虫子......
      【解决方案6】:

      我遇到了类似的问题,虽然我仍然不确定答案是什么,但我认为您的

       if(this.Dispatcher.Thread != Thread.CurrentThread)
      {
          this.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate{
              this.Name = value; // Call same setter, but on the UI thread
          });
          return;
      }
      

      应该替换为

       if(this.Dispatcher.CheckAccess())
      {
          this.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate{
              this.Name = value; // Call same setter, but on the UI thread
          });
          return;
      }
      

      CheckAccess 不会出现在 Intellisense 中,但它存在并用于此目的。另外,我同意通常您希望在这里使用 BeginInvoke,但是我发现当我执行此异步操作时我没有获得 UI 更新。不幸的是,当我同步执行时,我会遇到死锁情况......

      【讨论】:

        【解决方案7】:

        我知道这是一个旧线程,但这是另一个解决方案。

        我刚刚解决了一个类似的问题。我的调度员运行良好,所以...

        我必须显示 DEBUG -> THREAD WINDOW 来识别在任何地方执行我的代码的所有线程。

        通过检查每个线程,我很快就知道是哪个线程导致了死锁。

        它是多个线程结合lock (locker) { ... } 语句和对 Dispatcher.Invoke() 的调用。

        在我的情况下,我可以更改特定的 lock (locker) { ... } 语句,并将其替换为 Interlocked.Increment(ref lockCounter)

        这解决了我的问题,因为避免了死锁。

        void SynchronizedMethodExample() {
        
            /* synchronize access to this method */
            if (Interlocked.Increment(ref _lockCounter) != 1) { return; }
        
            try {
            ...
            }
            finally {
                _mandatoryCounter--;
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-03-08
          • 2022-12-27
          • 2013-04-18
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多