【问题标题】:DoEvents vs anything else --> for long COM operationDoEvents 与其他任何东西 -> 用于长时间 COM 操作
【发布时间】:2014-02-27 04:19:37
【问题描述】:

我有一个 WPF 程序,我的模型需要加载 "Out-of-Proc" (.exe) COM 组件,以便在用户在 UI 上执行操作时实现一些验证.我想通知用户,将进行长时间的操作,让他知道应用程序很忙,而不仅仅是冻结。但是 UI 上的任何操作都会在 COM 操作完成后发生。

我认为任何 COM 通信都应该在主 UI 线程上完成。它消除了在主 (UI) 线程之外的另一个线程上运行的任何解决方案。

我尝试了很多选项都没有成功:

我无法从需要刷新 UI 的模型中实现同步操作。 我的操作有一个属性“IsLoading”,我从我的视图中订阅了该属性,并且我尝试根据其状态更新 UI,但似乎在 WPF 中这是不可能的???

还有其他建议吗?

我可以使用 async/await 并从另一个运行另一个调度程序的线程执行我的 COM 操作(有点复杂),并且会失去所需的同步性(用户需要 COM 操作的结果才能继续其工作)?

主要针对盲人... 一些更清晰的解释(有关所需同步性的更多详细信息):

当用户单击 TreeView 项目时,我会加载一个网格,然后需要验证在网格中输入的数据是否仍然有效。要进行验证,我需要通过 COM 加载应用程序并自动加载文档,然后解析它并验证网格中的数据(在视图中的网格模型中)。这需要 10 秒。 如果我在另一个线程上执行此操作,那么用户可以执行一个操作来选择在网格中添加一个新行,该行仍然依赖于与前一个文档一起加载的同一个 COM 应用程序。我仍然需要等待应用程序加载。这是一个同步动作。我的应用程序依赖于该 COM 应用程序,其加载的文档处于有效状态,以便用户采取更多操作。但是我需要给用户一些关于我正在做什么的反馈(启动 COM 应用程序并加载文档)。在另一个线程上执行 COM 操作只是稍后报告问题,但不能解决用户需要等待操作完成的事实。我认为我需要(强制)更新我的 WPF 应用程序,但找不到任何(扭曲的)方法。

【问题讨论】:

  • 出于好奇,为什么要从主 UI 线程调用 COM?
  • 你认为为什么需要在 UI 线程中完成?
  • 据我所知,几乎所有 COM 组件都是 STA(单线程单元),应该从应用程序的第一个线程 (UI) 调用...因为 CoInitialize() 我认为?
  • 当使用托管线程中的 COM 对象时,CLR 将自动创建一个单线程单元并编组来自该单元的所有调用/返回。 See here for source。如果您使用的是 STA COM 对象,请确保将后台线程标记为 STA。
  • @Lukazoid,如果我理解正确,我可以将一个工作线程标记为 STA,然后在该线程上创建一个 COM 对象。但是该对象是可以直接从我的主线程访问,还是我必须通过之后创建它的线程。 (另外,有没有办法将它与我的主线程同步?...否则可能会发生预期 COM 操作完成但未完成的操作)?

标签: c# wpf multithreading com doevents


【解决方案1】:

我在 WPF 中使用过一次,以强制屏幕重新绘制: 我使用了 VB 的自动翻译,所以我希望它是正确的

private Action EmptyDelegate = () => { };
[System.Runtime.CompilerServices.Extension()]
public void Refresh(UIElement uiElement)
{
    uiElement.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Render, EmptyDelegate);
}

【讨论】:

  • 我想有人给你-1。这不是我。对不起。感谢您的尝试。我可以使用具有后台优先级的 BeginInvoke,但它会阻止我的代码同步(用户将能够在我的操作发生之前在屏幕上执行一些操作)
  • 另外,它发生在我的模型中。当我点击屏幕时,它第一次执行长动作。之后,无需任何操作。我知道我可以在调用我的模型操作之前检查我的模型状态,并在需要时显示 IsBusy 屏幕,但这对我来说听起来很糟糕。如果我改变我的模型,我需要改变我的视图(决定我是否忙的逻辑应该在模型中决定)。但老实说,我认为我会尝试您的(稍作修改)解决方案,并会做一些糟糕的编程异常以查看它是否有效。它不会是完美的,但我认为它应该有效!
  • 谢谢,我尝试了一些与您的建议相关的解决方案(例如 Microsoft 的 DoEvents:msdn.microsoft.com/en-us/library/…),但没有成功。我还没有找到它不起作用的确切原因。
【解决方案2】:

您可以在任何线程上创建和使用 COM 对象,如果您的应用程序使用 STA 线程模型,编组器将负责在后台线程上运行它。无需通过主 UI 线程汇集每个调用。

关于您在 cmets 中的最后一个问题,由于您将在后台线程上运行它,因此您当然需要像往常一样将结果与 lockInvoke 同步,完成后将结果返回到主线程。这与 COM 没有什么特别的关系,它只是 Windows 线程的工作原理。

简而言之,停止将 UI 线程用于繁重的、非 UI 相关的工作。

【讨论】:

  • 我添加了有关所需同步性的更多详细信息。我相信我不能在另一个线程上做任何事情。我看不出它的方式和好处......在这种情况下。我确实经常使用线程(任务,tpl 等),但在这种情况下......看不到好处......请参阅问题描述中的附加细节。
  • 停止专注于“解决方案”,并要求围绕您有缺陷的设计重新设计整个操作系统。您单击树视图节点,禁用树视图,将“请稍候”控件设置为可见(漂亮的动画轮等)并让您的线程完成其工作。完成后,隐藏封面,重新启用树视图并向用户提供反馈(或者说耶!并清除框,或突出显示错误并显示适当的错误消息)。我再说一遍,无论你认为多么合适,都不要阻塞主 UI 线程。曾经。完全没有。你的任何理由都是错误的。
  • 我同意我一般不应该阻塞主线程。我的代码执行延迟加载,并且在大型操作过程中可以从许多地方访问 COM 对象。当它是单个任务的一部分时很容易在另一个线程上加载,但是当您必须修改许多地方并对可能如此简单的事情进行相当大的修改时,我倾向于认为应该可以阻止在那些特定情况下的主线程。关于有缺陷的设计......是的,我想从事其他工作,但有些事情我无法控制。
  • 您的解决方案非常接近 Noseratio。我可能会尝试实现它,尽管由于将我的代码(许多地方)转换为异步的大量工作会影响我的交付时间。我真的认为这是有问题的,因为当我加载 COM 对象时,无论如何我都必须锁定 75% 的用户界面。当我为用户提供 25% 的界面可以工作的优势与所有复杂的操作和维护以及时间到位时......我会说我选择简单的扭曲方式来更新界面:-s !
【解决方案3】:

[UPDATE] 由于 OP 已更新问题并指定他正在使用 out-of-proc COM 对象,因此下面描述的自定义 STA 线程管道不会使感觉。现在,一个简单的await Task.Run(() => { /* call the out-of-proc COM */}) 就足以让 UI 保持响应。感谢 @acelent 澄清这一点。


最近我回答了一个相关问题:StaTaskScheduler and STA thread message pumping

solution 用于在专用后台 STA 线程上创建和使用 STA COM 对象,该线程为这些 COM 对象提供消息泵送和线程关联。

我想通过async/await 展示如何在您的情况下使用ThreadWithAffinityContext

dynamic _comObject = null;

ThreadWithAffinityContext _staThread = null;

// Start the long-running task
Task NewCommandHandlerAsync()
{
    // create the ThreadWithAffinityContext if haven't done this yet
    if (_staThread == null)
        _staThread = new ThreadWithAffinityContext(
            staThread: true,
            pumpMessages: true);

    // create the COM Object if haven't done this yet
    if (_comObject == null)
    {
        await _staThread.Run(() =>
        {
            // _comObject will live on a dedicated STA thread,
            // run by ThreadWithAffinityContext
            _comObject = new ComObject();
        }, CancellationToken.None);
    }

    // use the COM object
    await _staThread.Run(() =>
    {
        // run a lengthy process
        _comObject.DoWork();
    }, CancellationToken.None);
}

// keep track of pending NewCommandHandlerAsync
Task _newCommandHandler = null;

// handle a WPF command
private async void NewCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
    try
    {
        // avoid re-entrancy (i.e., running two NewCommandHandlerAsync in parallel)
        if (_newCommandHandler != null)
            throw new InvalidOperationException("One NewCommandHandlerAsync at a time!");
        try
        {
            await _newCommandHandler = NewCommandHandlerAsync();
        }
        finally
        {
            _newCommandHandler = null;
        }
    }
    catch (Exception ex)
    {
        // handle all exceptions possibly thrown inside "async void" method
        MessageBox.Show(ex.Message);
    }
}

我们将冗长的进程_comObject.DoWork() 卸载到单独的线程这一事实并不能自动解决其他常见的 UI 相关问题:

冗长的后台操作挂起时如何处理UI?

有多种选择。例如,您可以禁用触发 NewCommand_Executed 事件的 UI 元素,以避免重新进入,并启用另一个 UI 元素以允许用户取消待处理的工作(Stop 按钮)。如果您的 COM 对象支持,您还应该提供一些进度反馈。

或者,您可以在启动长时间运行的任务之前显示一个模式对话框,并在任务完成后将其隐藏。就 UI 可用性而言,模态不太理想,但很容易实现 (example)。

【讨论】:

  • 哇!伟大的 !我找到了一种扭曲的方式来做我想做的事,并试图将它打包到一个可重用的控件中(它工作得很好,但它非常扭曲)。但有一件事是肯定的......我绝对必须尝试你的解决方案!它看起来很锋利,听起来很有前途。我可能会花几个小时(见回答前几天),但与此同时......非常感谢!!!!对我来说,它看起来是最好的解决方案,也是一个很好的例子!
  • @EricOuellet,没问题。请注意,如果您的 ComObject 提供了一些事件(例如,进度事件),您也可以在 await 中处理它们。但是您需要使用 dispatcher.BeginInvokeIProgress<T> 模式将更新传播到 UI 线程。
  • 2 件事: 1 - 我的 COM 对象没有任何事件,并且缺少事件在某些地方给我带来了一些困难(我需要使用极化,grrrr)。 2 - 正如 Eric Brown 在对我的问题的评论中提到的那样,听起来在工作线程上创建的 STA COM 对象将需要通过来自另一个线程(前 UI 线程)的一些工件来访问?如果是真的,这听起来是在给原本不简单的解决方案增加更多复杂性?
  • @EricOuellet,如果您要使用我的解决方案,您必须始终通过await _staThread.Run(() => { /* do stuff like polling etc */}) 访问对象。这是您为保持 UI 线程响应而付出的代价。不过,您别无选择,DoEvents 就是 really a Pandora's Box。您可以拥有多个公寓,例如_staThread,但每个 COM 对象应该只属于其中一个。
  • 从问题“要进行验证,我需要通过 COM 加载应用程序并自动加载文档”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-26
  • 2020-07-21
  • 2011-01-28
  • 1970-01-01
相关资源
最近更新 更多