【问题标题】:Can I use Invoke instead of BeginInvoke to address suspension of Dispatcher processing?我可以使用 Invoke 而不是 BeginInvoke 来解决 Dispatcher 处理的暂停问题吗?
【发布时间】:2020-01-01 13:23:24
【问题描述】:

我们的一位客户报告了我们的一个加载项变得无响应的问题。报告的错误包含深入 WPF 内部的堆栈跟踪,我不会因此而分散您的注意力。可以说顶级错误为:

调度程序处理已暂停,但消息仍在处理中。

相同的代码在我的机器上完美运行,据我所知,任何其他机器。代码所做的只是实例化一个 WPF 窗口并调用它的 .ShowDialog() 方法。

查看具有相同错误的问题,我找到了如下答案: Dispatcher throws InvalidOperationException on Messagebox.Show in Textchanged event 要么 WPF : Dispatcher processing has been suspended, but messages are still being processed

在这两种情况下,提供的答案都是使用 Dispatcher.BeginInvoke

问题在于此调用是异步,我需要等待用户响应。所以我改用 Dispatcher.Invoke:

Dispatcher.Invoke(Sub() oAIC.ShowDialog(), System.Windows.Threading.DispatcherPriority.Normal)

这又一次在我的机器上完美运行,但这没有任何意义。原始代码在我的机器上也能完美运行。

所以我的问题是:

这还值得追求吗?或者我使用 .Invoke 而不是 .BeginInvoke 的事实是否等同于我直接使用原始的 .ShowDialog() 所以我在浪费时间?

只要说有问题的客户不是合作型的就够了。其他客户会很高兴我给他们一个试用版并报告回来,但这个人只是想要一个解决方案,不想被其他任何事情打扰。由于任何其他客户从未报告过这件事,我什至找不到另一只“豚鼠”来试穿,所以我剩下的就是在这里问它的选项,希望有足够专业知识的人可以告诉我“是的,这是一个很好的前进方向”或“你在浪费时间”。抱歉,如果这不太适合在这里提问的模型,但我已经走到了尽头......

在看到 Peregrine 的回答后进一步澄清

代码是 Office 插件(在本例中为 Microsoft Word)的一部分。我试图显示的 WPF 对话框从功能区栏中的按钮单击向下弹出几个级别。

我把按钮点击后面的代码放到一个函数中

DoTheButtonCode()

并叫它如下

Await System.Threading.Tasks.Task.Run(AddressOf DoTheButtonCode)

当我尝试这个时,出现了一个错误:

PresentationCore.dll 中出现“System.InvalidOperationException”类型的异常,但未在用户代码中处理 附加信息:调用线程必须是 STA,因为许多 UI 组件都需要这个。

现在这很奇怪。在原始按钮单击事件中,system.threading.thread.currentthread 的公寓状态是 STA 但是当我进入 DoTheButtonCode 并检查 system.threading.thread.currentthread 它的公寓状态是 MTA p>

进一步说明

好的 - 尝试了其他方法:

将 DoTheButtonCode 更改为异步函数:

Private Async Function DoTheButtonCode() As Threading.Tasks.Task(Of Integer)

按钮 Click 事件处理方法现在定义为 Private Async Sub,我在其中调用

Await DoTheButtonCode()

这实际上是可行的,除了出现在 DoTheButtonCode 的函数定义中的这个琐碎的警告:“这个异步方法缺少 'Await' 运算符,因此将同步运行”(等等)

因此,虽然这确实有效,但我怀疑我在这里是在开玩笑。

我想我已经准备好放弃了。我将在我原来的 ShowDialog 调用周围添加一个 try/catch 构造,如果至少引发错误,它只会什么都不做,而不是崩溃:(

感谢您的所有帮助,Peregrine

【问题讨论】:

  • Task.Run 将使用默认调度程序,该调度程序将调度到线程池线程,该线程不是 STA。您可以编写一个自定义调度程序,它将调度到 STA 线程上;我把它留给读者作为练习,使用谷歌应该很容易找到示例。对于与 UI 相关的事情,更大的问题是它会将其放在非 UI 线程上,因此如果您想在那里执行与 UI 相关的事情,则需要将其分派回DoTheButtonCode 中的 UI 线程。
  • 如果你想等待用户响应,你应该可以AwaitBeginInvoke调用;事实上,根据我的经验,如果你等待它,你会得到一个编译器错误(对于我想要触发并忘记 BeginInvoke 的实例,我在我的代码中警告禁用 pragma)。
  • 谢谢克雷格。我会记住这一点,以防我不得不重新审视我在 OP 结尾处所述的“钝器”方法。现在我只会抓住错误,忽略它,什么也不做,希望这对这个特定用户来说是一个“一次性”问题,如果他们第二次点击按钮就可以了,就像字面意思一样其他人。如果失败了,是的,我会考虑创建一个自定义调度程序,但我希望我不必这样做:)
  • 我记错了,你甚至不一定需要编写它,有一个包含 STA 任务调度程序的 Microsoft 并行扩展附加包。
  • 感谢 Craig 的额外澄清。

标签: c# .net wpf vb.net


【解决方案1】:

我正在我的库中做一些非常相似的事情,以纯 MVVM 方式显示消息对话框。

我的代码和你的代码之间唯一的区别是

  1. 我使用的是 Dispatcher.InvokeAsync() 而不是 Dispatcher.Invoke() - 这使得呼叫可以等待。

  2. 我正在 Dispatcher.BeginInvoke() 调用中创建我的对话窗口 - 不清楚您的 oAIC 窗口是在哪里创建的。

private async Task<perDialogButton> _ShowDialogAsync(object viewModel, object content, string title, perDialogButton buttons, perDialogIcon dialogIcon)
{
    // get control associated with ViewModel instance
    var associatedControl = ... 

    return await associatedControl.Dispatcher.InvokeAsync(() =>
    {
        var window = new perDialog
        {
            // set dialog properties 
            ... 
        };

        window.ShowDialog();

        return window.SelectedButton;
    });
}

模式详情,以及我的blog post上的完整源代码

【讨论】:

  • 请注意,您也可以await BeginInvoke。 BeginInvoke 和 InvokeAsync 之间的重要区别在于参数类型。
  • 非常感谢 Peregrine,但我认为我正在打一场失败的战斗......我将使用回答您的问题按钮在下面发布解释,因为我无法包含足够的内容评论中的详细信息.....忍受我:)
  • 好吧,这似乎不是正确的做法,所以我将通过编辑问题来进一步澄清。
  • @Peregrine - 我在原始帖子中添加了进一步的说明。感谢您的帮助,但我想我已经碰到了众所周知的砖墙。希望这对其他人有一些有限的用途。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-06
  • 1970-01-01
  • 1970-01-01
  • 2018-08-29
  • 1970-01-01
相关资源
最近更新 更多