22

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

一般流程是:

  1. 处理按钮上的点击事件
  2. 创建一个新线程(STA):创建演示者和 UI 的新实例,然后调用方法Disconnect
  3. 然后断开连接在 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 实现。

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

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

4

7 回答 7

11

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

于 2008-11-05T06:01:49.290 回答
8

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

线程 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 将排队另一个调度项。这个项目永远不会被解雇,因为你的调度员永远不会完成你之前的请求;它已经卡住了。

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

于 2014-02-28T17:32:25.390 回答
5

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

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

于 2008-11-10T16:54:58.300 回答
3

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

我也对 Invoke 感到困惑,或者如果我可以 BeginInvoke,我的代表没有被调用 - 似乎在做所有事情:-(

于 2011-04-08T16:23:54.450 回答
2

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

于 2008-11-05T06:33:09.117 回答
1

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

 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 更新。不幸的是,当我同步执行时,我会遇到死锁情况......

于 2011-03-07T04:19:12.360 回答
0

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

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

我必须显示 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--;
    }
}
于 2013-01-10T09:15:53.367 回答