【问题标题】:Dispatcher.Invoke blocks foreverDispatcher.Invoke 永远的块
【发布时间】:2010-06-23 17:39:32
【问题描述】:

我正在尝试在 UI 调度程序上调用一个对话框:

class DialogService : IDialogService
{
    private readonly Dispatcher _dispatcher = Application.Current.Dispatcher;

    public bool? Show(IDialogViewModel viewModel)
    {
        if (_dispatcher.CheckAccess())
        {
            var dialogWindow = new DialogWindow();
            return dialogWindow.Show(viewModel);
        }
        else
        {
            Func<IDialogViewModel, bool?> func = Show;
            return (bool?)_dispatcher.Invoke(func, viewModel);
        }
    }
}

但是,对Invoke 的调用将永远阻塞,并且在 UI 线程上永远不会调用 Show...

使用BeginInvoke 不是一个选项:我需要立即得到结果,因为我正在处理来自远程对象的事件(使用 .NET 远程处理)

有什么想法吗?


更新

这里有一个更完整的问题描述:

我有一个使用 .NET Remoting 与 Windows 服务通信的客户端应用程序。在某些时候,客户端调用服务以执行操作(此调用由用户操作触发,在这种情况下单击按钮)。服务可能需要凭据来执行操作:在这种情况下,它会引发 CredentialsNeeded 事件,由客户端处理。然后客户端显示一个对话框以提示用户输入凭据,并在事件的参数中设置适当的属性。当事件处理程序返回时,服务使用凭据完成操作,并将控制权返回给客户端。

所以,当我收到事件时,UI 线程正在等待服务端的操作完成...我认为这是未处理 Invoke 调用的原因,但我该如何解决它?我可以创建 另一个 UI 线程来显示对话框吗?在 WinForms 中,我知道我可以使用 Application.Run 启动另一个消息泵,但我不知道如何在 WPF 中执行相同操作...

【问题讨论】:

    标签: wpf invoke dispatcher


    【解决方案1】:

    在此方法调用期间,您是否拥有 UI 线程上的另一个方法试图获取的锁?那肯定可以解释它。

    每次都会发生这种情况吗?这显然会使诊断变得更容易。

    对我来说不同寻常的是,我建议使用调试器:只需点击 break 并查看线程在做什么。

    最后,我知道您需要结果...但是如果您这样做改为调用BeginInvoke(并返回一个虚拟值)会发生什么?这会调用调度程序中的方法吗?显然这不会是一个长期的解决方案,但它会提供更多的诊断信息。

    【讨论】:

    • 感谢您的回答。我没有在代码的那部分进行任何显式锁定,所以我认为问题不存在。是的,它确实每次都会发生......实际上我想我开始明白出了什么问题(请参阅我更新的问题),我只是不知道如何解决它......
    【解决方案2】:

    我最终找到了解决问题的方法:我只需要在一个新线程上显示对话框,并使用它自己的调度程序。这是修改后的代码:

    class DialogService : IDialogService
    {
        private readonly Dispatcher _dispatcher = Application.Current.Dispatcher;
    
        public bool? Show(IDialogViewModel viewModel)
        {
            if (_dispatcher.CheckAccess())
            {
                DoShow(viewModel);
            }
            else
            {
                bool? r = null;
                Thread thread = new Thread(() => r = DoShow(viewModel));
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
                thread.Join();
                return r;
            }
        }
    
        private static bool? DoShow(IDialogViewModel viewModel)
        {
            var dialogWindow = new DialogWindow();
            return dialogWindow.Show(viewModel);
        }
    }
    

    【讨论】:

    • BeginInvokeInvokeAsync 可以解决您的问题。多个 STA 线程可能很臭。顺便说一句,你现在可能已经用thread.Join(); 阻塞了你的 worker 线程
    • @MickyD 正如我在问题中提到的那样,BeginInvoke 不是一个选项,因为我需要同步获取对话结果。无论如何,那是很久以前的事了,这个解决方案对我有用;)
    【解决方案3】:

    在您尝试使用 Invoke 时,UI 线程是否对其他东西(可能是您的后台线程)进行了阻塞调用?如果是这样,那么您手上有一个经典的僵局。两个线程各自等待另一个返回。

    在 Windows 窗体中,它们经常在您最不期望的情况下在幕后执行大量“消息泵送”,以避免死锁,但很多时候它会产生更多问题,并且由于意外重新进入而难以发现错误。

    如果您认为您的 UI 线程没有在进行阻塞调用,您应该在调试器中运行应用程序,并在发生死锁时进入调试器。然后在线程窗口中查找主线程。双击主线程,然后查看调用堆栈窗口以查看主线程所在的位置。

    您也可以尝试明确指定发送的 DispatcherPriority,但我认为如果存在真正的死锁,这并不重要。

    【讨论】:

    • 感谢您的回答。实际上,我的主线程正在对远程对象进行阻塞调用,而远程对象本身也在对客户端进行阻塞调用……远程对象正在引发一个事件以提示用户输入凭据。那就是我试图显示一个对话框的地方。有关详细信息,请参阅我更新的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-03
    • 2013-10-02
    相关资源
    最近更新 更多