具有多个STA线程的Wpf应用程序仍会阻止用户界面

时间:2014-10-05 09:50:17

标签: c# wpf ironpython dispatcher sta

不久前,我们使用IronPython将Python脚本添加到Wpf应用程序中。起初,它只是“奴隶”,因为例如按钮点击调用脚本然后只是运行完成将控制权返回给Wpf。后来我们添加了'master'脚本:脚本在它自己的线程中运行,并控制应用程序的其余部分。这是相当具有挑战性的,但过了一段时间,在现有的SO内容的帮助下,我们看起来很有效。从来没有真正使用它,直到现在,不幸的是事实证明它不能正常工作。核心原因是虽然有两个单独的STA线程(主要的Wpf一个和一个脚本),因此两个不同的Dispatcher实例,主线程似乎被阻止,因为脚本线程在循环中等待主线程完成(响应在脚本线程上处理的按钮单击并在主线程上启动事件)。使用具有单独ui窗口的两个线程的全部意义当然不会发生。发生了什么事?

更新它可以使用最小code重现,所以我链接到它而不是在这里发布伪代码。在创建代码时,我发现当脚本线程创建的窗口未嵌入(设置MainWindow.hostedWin = false)时,不会发生死锁,并且一切都按预期运行。

回应评论因此有3个关注线程正在发挥作用。我们称它们为Python,Ui和Process。 Python启动Process并等待它完成。进程调用在Ui上调用。在那一点上不应该做任何事情:毕竟,它是阻塞的Python,而不是Ui,而这个结构的重点是Ui不应该与Python交互。好吧,除了它确实以某种方式。哪个是罪魁祸首。在僵局中,Ui位于PresentationFramework.dll!System.Windows.Interop.HwndHost.OnWindowPositionChanged(System.Windows.Rect rcBoundingBox) + 0x82 bytes,Process位于WindowsBase.dll!System.Windows.Threading.DispatcherOperation.DispatcherOperationEvent.WaitOne() + 0x2f bytes,Python位于Thread.Sleep

这里发生了什么,以及如何解决它?

3 个答案:

答案 0 :(得分:7)

我会保持简短,这个答案会让你开心的几率很小。这是一个三方僵局。主线程和PythonThread之间交互中最严重的一个。这种死锁发生在Windows内核中,NtUserSetWindowPos()调用无法进行。它被阻塞,等待PythonThread上的WM_LBUTTONUP回调通知完成运行。

此死锁是由您的WpfHwndEmbedHost黑客引起的。将另一个线程或进程拥有的顶级窗口转换为子窗口是一个appcompat功能,旨在支持Windows 3.x程序。一个尚未支持线程的Windows版本,其中一个任务嵌入另一个任务的窗口并不是问题。说得温和,WPF窗口并不像这样的窗口。否则,一个众所周知的麻烦制造者,因为在浏览器窗口中嵌入Acrobat Reader的原因非常糟糕。 打开WS_CHILD样式标志应该会带来缓解,但WPF并不高兴。只需将hostedWin设置为false即可解决问题。

另一个死锁是我警告过的,主线程和ProcessThread之间的交互。 Dispatcher.Invoke()是危险的,它死锁,因为主线程卡在内核中。使用Dispatcher.BeginInvoke()解决了这个问题。部分地,你仍然有主线程精神紧张5秒钟。

最严重的问题是内核锁定,它会以许多其他方式咬人。您将不得不将其保留为单独的窗口以避免它。不是好消息,我敢肯定。

答案 1 :(得分:1)

这是一个很长的镜头,但你可能必须实现自己的SynchronizationContext来实现这一点。

据我了解<{3}}及其参考资料,似乎CLR对UI线程的消息泵有自己的想法,而且它实际上不可能在一个窗口下运行两个UI线程(BTW,我能够在没有IronPython的情况下复制问题,这似乎与此无关)

主要参考来自cbrumme的WebLog&#39; Andrew Nosenko's answer&#39;

  

我一直说托管阻塞会在STA线程上调用时执行“一些抽取”。要确切知道什么会被泵送,这不是很好吗?不幸的是,抽水是一种超越凡人理解的黑色艺术。在Win2000及以上版本中,我们只需委托OLE32的CoWaitForMultipleHandles服务。在我们为NT4和Win9X编写初始剪切代码之前,我想我会浏览CoWaitForMultipleHandles以了解它是如何完成的。这是很多很复杂的代码页面。它使用Win9X甚至无法使用的特殊标志和API。

我必须承认我在这里有点偏僻,并且可能完全忽略了这一点,所以如果这可能根本不是问题的答案那么道歉(尽管这是一次很好的体验)对我来说,这是肯定的。)

我尝试使用Andrew Nosenko的SynchronizationContext实现来提供一个不幸的例子但没有成功。希望它能帮助你,祝你好运!

答案 2 :(得分:0)

我已经在我的应用程序中遇到了类似的问题,我从重负载线程调用了一些UI更新,并且UI阻止了线程的结果相同。我提出了一个解决方案,我现在在每个应用程序中使用它,虽然你需要将它应用到你的应用程序,它的工作原理如下:

除了你在(和UI线程)工作的线程之外,你还需要创建另一个线程,这个线程将从堆栈中获取数据并将其发送到UI线程。

当您希望UI通过工作线程获得更新时,您可以将工作线程的结果保存到List中,如果其更复杂的数据,则需要创建结构并将所有当前数据从线程保存到结构并将其添加到List(将数据添加到列表中不需要调用)。

现在你的第二个线程在一个循环中运行,如果列表中有某些内容,那么如果列表元素已经添加到你的UI中,则会以一定的间隔进行检查。

以下是一个如何运作的示例

using System;
using System.Windows;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;

namespace nonblock
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private ListBox l1;
        private ListBox l2;

        private Thread workThread;
        private Thread nonBlockThread;

        private List<TwoNumbers> list;
        private void Form1_Load(object sender, EventArgs e)
        {
            this.Size = new Size(500,500);
            this.FormClosing += (ss, ee) =>
            {
                workThread.Abort();
                nonBlockThread.Abort();
            };
            l1 = new ListBox();
            l1.Dock = DockStyle.Left;
            l2 = new ListBox();
            l2.Dock = DockStyle.Right;
            list = new List<TwoNumbers>();

            this.Controls.Add(l1);
            this.Controls.Add(l2);

            workThread = new Thread(work);
            workThread.Start();

            nonBlockThread = new Thread(update);
            nonBlockThread.Start();
        }

        private void work()
        {
            int a = 0;
            int b = 0;
            int counter = 0;
            Random r = new Random();
            while (true)
            {
                a += r.Next();
                b += r.Next();
                counter++;
                if (counter % 10 == 0)
                    list.Add(new TwoNumbers(a, b));
                Thread.Sleep(40);
            }
        }

        private void update()
        {
            while (true)
            {
                if (list.Count > 0)
                {
                    for (int a = 0; a < list.Count; a++)
                    {
                        l1.Invoke((MethodInvoker)(() => l1.Items.Add(list[0].n1)));
                        l2.Invoke((MethodInvoker)(() => l2.Items.Add(list[0].n2)));
                        list.RemoveAt(0);
                    }
                }
                Thread.Sleep(1000);
            }
        }

        public class TwoNumbers
        {
            public int n1 { get; set; }
            public int n2 { get; set; }

            public TwoNumbers(int a, int b)
            {
                n1 = a;
                n2 = b;
            }
        }
    }
}