2

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

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

我尝试了很多选择但没有成功:

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

还有其他建议吗?

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

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

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

4

3 回答 3

4

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

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

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

于 2014-02-03T16:45:06.227 回答
4

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


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

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

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

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 可用性而言,模态不太理想,但它很容易实现(示例)。

于 2014-02-04T01:00:02.140 回答
0

我曾将它用于 WPF,以强制屏幕重新绘制:我使用了 VB 的自动翻译,所以我希望它是正确的

private Action EmptyDelegate = () => { };
[System.Runtime.CompilerServices.Extension()]
public void Refresh(UIElement uiElement)
{
    uiElement.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Render, EmptyDelegate);
}
于 2014-02-03T15:46:03.177 回答