我有一个有点复杂的WPF应用程序,当试图使用调度器调用UI线程上的调用时,它似乎"挂起"或陷入等待调用。
一般流程为:
- 处理按钮上的单击事件
- 创建一个新线程(STA),该线程:创建演示者和UI的新实例,然后调用方法Disconnect
- Disconnect然后在UI上设置一个名为Name的属性
- 然后,Name的setter使用以下代码来设置属性:
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()-感谢所有提供帮助的人。
Invoke是同步的-您需要Dispatcher.BeginInvoke。此外,我认为您的代码示例应该在"else"语句中移动"SetValue"。
我认为这最好用代码来显示。考虑这个场景:
线程A这样做:
lock (someObject)
{
// Do one thing.
someDispatcher.Invoke(() =>
{
// Do something else.
}
}
线程B这样做:
someDispatcher.Invoke(() =>
{
lock (someObject)
{
// Do something.
}
}
乍一看,一切似乎都很美好,但事实并非如此。这将产生僵局。调度程序就像线程的队列,当处理这样的死锁时,重要的是要这样想:"以前的哪个调度可能会阻塞我的队列?"。线程A将进入…并在锁下调度。但是,如果线程B在线程A位于标记为"Do one thing"的代码中的时间点出现,该怎么办?好
- 线程A锁定了someObject,并且正在运行一些代码
- 线程B现在进行调度,调度程序将尝试获取someObject的锁,由于线程A已经有了锁,因此会阻塞您的调度程序
- 然后,线程A将对另一个调度项目进行排队。此项目永远不会被激发,因为您的调度程序永远不会完成对您以前请求的处理;它已经堵塞了
你现在有一个漂亮的僵局。
您说您正在创建一个新的STA线程,这个新线程上的调度器正在运行吗?
我从"this.Dispatcher.Thread!=Thread.CurrentThread"中了解到,您希望它是一个不同的调度器。请确保它正在运行,否则它将不会处理其队列。
我想你的意思是如果(!this.Dispatcher.CheckAccess())
我也在使用Invoke挂起,或者如果我可以BeginInvoke,我的委托没有被调用-似乎在按书做所有事情:-(
这听起来像是一个死锁;如果调用.Invoke的线程已经持有UI线程完成其工作所需的锁/互斥锁等,则通常会发生这种情况。最简单的方法是使用BeginInvoke:这样,当前线程就可以继续运行,并且(可能)很快就会释放锁,让UI获得它。或者,如果你能识别出有问题的锁,你可以故意释放它一段时间。
我遇到了类似的问题,虽然我仍然不确定答案是什么,但我认为你的
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更新。不幸的是,当我同步执行时,会出现死锁情况。。。
我知道这是一个旧线程,但这里有另一个解决方案。
我刚刚解决了一个类似的问题。我的调度员运行良好,所以…
我必须显示DEBUG->THREAD窗口来识别在任何地方执行我的代码的所有线程。
通过检查每个线程,我很快就发现是哪个线程导致了死锁。
它是多个线程组合一个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--;
}
}