【问题标题】:MultiThreading COMObject and UI Thread (C#)多线程 COMObject 和 UI 线程 (C#)
【发布时间】:2013-03-01 05:01:17
【问题描述】:

这是我在这里的第一篇文章,因为实际上我通常用真棒解决我所有的问题 您可以在此处找到发布数据库。但我现在实际上被困住了:

我正在开发一个遵循 MVVM 的项目,其中包括一个 COM 对象。 正如我在研究期间所读到的,我知道 COM 对象只能从创建它的线程访问。我的 COM 对象实现了以下接口

interface IComUpdate
{
    void Update();
}

所以当我创建我的 COM 对象时,每次有更新(我不知道什么时候,它是随机的)COM 服务器都会调用我实现的 COM 对象类的Update()

我的目标是创建一个不同的线程,命名一个 COM 对象线程,其中 COM 对象独立于我的 UI 线程而存在,因此每次有更新时,我都会在与 UI 线程不同的线程中处理它。

实际上它正在工作:

在 ViewModel 的开头,我创建了一个特定对象的集合。

这个对象,我们称之为ModelObj,是模型的一部分,它定义了一个静态构造函数,应用程序除了初始化一些变量外,还为COM对象创建和启动一个新线程:

Thread t = new System.Threading.Thread(() =>
           {
               System.Threading.Thread.CurrentThread.Name = "Thread of COM Object";
               IComUpdate myComObj;
               myComObj = (IComUpdate)Activator.CreateInstance(blabla);
               Application.Run();
           });

t.SetApartmentState(ApartmentState.STA);
t.Start();

它实际上工作得很好,在我的 COM 对象的Update() 实现中,我实际上看到线程是刚刚创建的线程,而不是 UI 线程。

现在的问题是:我创建的这个 ModelObj 实现了 INotifyPropertyChanged 接口。

我的想法是:每次 COM 对象收到更新时,我都会处理来自 COM 对象线程的数据,并从该线程更新我的 ModelObjinstance 的某些属性,因此这些属性会引发属性更改我的ModelObj 和 UI 线程将更新用户界面。

如果 UI 更新花费太多时间,我可能会错过一些 Update() 出现在屏幕上,但 COM 对象会将它们记录在我的 ModelObj 实例中,因此 UI 捕获所有更新,我只是不希望 COM 对象必须等待 UI 更新才能再次调用。

我阅读了大量帖子,然后认为我的RaisePropertyChanged("property") 会失败。

实际上,即使在 COM 对象的线程中,RaisePropertyChanged 也成功执行,因此跟踪我的代码,我看到它切换到我做的 ViewModel 程序集

// Here I'm still in the thread of my COM object!
base.NotifyOfPropertyChange<string>(() => this.property)

然后是 UI 更新。

注意:我正在使用 Caliburn Micro 来绑定我在 WPF 中的视图和我的 ViewModel。

所以我无法追踪这个base.NotifyOfPropertyChange&lt;string&gt;(() =&gt; this.property)。也许 Caliburn 处理线程切换,这不是我的问题。

我能说的是,我的 COM 对象线程等待 UI 更新以到达我的RaisePropertyChanged("property") 之后的下一条指令,所以它与 UI 线程完成整个工作完全一样。

我希望我的 COM 对象线程更新我的 ModelObj,这将向 UI 发送一条更新消息(因为此 ModelObj 的某些字段已更改)并继续立即,无需知道 UI 是否真的更新了。

有人知道这种行为吗?

非常感谢。

####UPDATE####

感谢大家这么快的回答。

我确实按照 Zdeslav Vojkovic 的建议做了:

您应该始终从 GUI 线程更新 GUI

为了完整起见,我是这样做的:

因为我的视图是完整的 WPF,没有后面的代码我没有任何控件或表单可以调用 BeginInvoke,所以在我的 ModelObj 的静态构造函数中,我从 UI 线程构建了一个不可见的控件,只是为了能够调用BeginInvoke 就可以了。

所以我宣布了它:

public static Control mInvokeControl;
delegate void MyDelegate();
private MyDelegate _NotifyDelegate;

然后在我的 Object 的静态构造函数中执行此操作:

mInvokeControl = new Control();
mInvokeControl.CreateControl();

在普通构造函数中,我以这种方式初始化委托:

_NotifyDelegate = new MyDelegate(this.NotifyByInvoke);

然后我就这样用了之后:

ModelObj.mInvokeControl.BeginInvoke(this._NotifyDelegate );

方法是:

public void NotifyByInvoke()
{
    RaisePropertyChanged("Update");
}

一切正常!

【问题讨论】:

    标签: c# multithreading user-interface com


    【解决方案1】:

    COMObj 只能从创建它的线程访问

    这不是真的。它取决于对象单元模型,但通常您可以从任何线程访问它,它将在同一线程上调用或编组到适当的线程。

    我相信您的问题是您从后台线程更新 GUI,这是一个主要的禁忌。您应该始终从 GUI 线程更新 GUI。当您更新模型对象时,它仍然会在后台线程上发生,并且INotifyPropertyChanged 接口的事件会在该线程上触发。

    您需要使用类似这样的方式将模型更新同步到 GUI 线程(WinForms,而不是 WPF - 在 WPF 中您应该使用 frm.Dispatcher.BeginInvoke,但问题是一样的):

    私有委托 void ExecuteActionHandler(Action action);

    public static void ExecuteOnUiThread(this Form form, Action action)
    {
      if (form.InvokeRequired) { // we are not on UI thread
        // Invoke or BeginInvoke, depending on what you need
        // but you said ' and continue immediatly' so BeginInvoke it is 
        form.BeginInvoke(new ExecuteActionHandler(ExecuteOnUiThread), action);
      }
      else { // we are on UI thread so just execute the action
        action();
      }
    }
    

    another question with similar problem,我在那里提供了更多详细信息。

    【讨论】:

    • “COMObj 只能从创建它的线程中访问”,实际上你是对的。我只是为其他人添加 cmets:如果我让我的新线程 MTA,COMObj 驻留在主线程中(因为它是第一个调用 CoInitialize 我理解的东西)。但是 ThreadingModel(在注册表编辑器中查看)是 Apartment,这就是为什么在我的具体情况下是这样的。
    • 我只是有一个遗留问题,假设我调用 BeginInvoke 过程,但正如我所说,更新 UI 比处理数据需要更多时间,我将在上一个操作之前进行其他 BeginInvoke 调用在 UI 线程上终止。我的电话是丢失还是排队?我宁愿它丢失。
    • 不确定 WPF,因为我不使用它,但对于 WinForms,调用被排队,因为它们实际上是由 PostMessage 处理的。如果 WPF 表单不使用一些类似的技术,我会感到惊讶——确保调用不会丢失,但我不确定执行顺序是否得到保证。在此处查看更多信息:stackoverflow.com/a/2411183/1663919
    • 最后我决定删除我对不可见控件的使用并使用 Dispatcher 类:msdn.microsoft.com/en-us/library/ms615907.aspx 我有一个静态变量使用 DispatcherOperation 获取 BeginInvoke 的结果,我检查操作是否通过status 属性,如果不是我发送更新,而是使用 DispatcherPriority.Inactive 发送消息,但不会发生任何事情,我不会使整个事情饱和。
    【解决方案2】:

    我不知道您处理了多少数据,或者执行 GUI 部分需要多少时间。您也可以考虑使用锁定队列。您可以使用 ModelObj 中的队列将新任务排入队列。你用你得到的一切来做这件事。那么你可能有一个计时器线程(在 GUI 线程上)。

    在这里您只需检查锁定队列,是否有一些新数据要显示在 GUI 上。您可以在本地将完整列表出列。然后您还可以检查一个组件上是否显示多个数据。通过这种方式,您可以跳过已经有较新更新的更新。 而且您跳过了调用 gui 线程来执行操作的时间。您可以一次进行多个 GUI 更新。如果您有太多事情要做,您可以只出列特定数量的项目,以让 GUI 对用户交互做出反应。但是,您需要检查队列是否不断增长。

    【讨论】:

    • 感谢您的回答,我确实考虑过您的描述,但不想使用计时器。我的数据在几微秒内处理完毕,在 c++ 中使用高度优化的库,UI 更新使它爆炸到 1 到 20 毫秒。 (它是一个粒子物理应用程序)
    • 我的意思是仅将计时器用于 GUI,而不是数据处理。您还可以将粒子收集在 2D 阵列(X、Y 位置)中,并且可以将其保存在时间幻灯片中。然后你可以定义一个平均窗口,例如0.5 秒。您在数据线程中填充数组,并不时将渲染的组合图像推送到 GUI。
    • 是的,实际上我已经使用了一个缓冲区,实际上每个 X 条目都会通知我的 GUI,计时器将允许我定期更新,这是一个想法,我会看看它,或者让它成为一个选项。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-19
    • 2016-12-19
    • 1970-01-01
    • 2015-08-29
    • 1970-01-01
    • 2017-04-08
    相关资源
    最近更新 更多