3

这是我在这里的第一篇文章,因为实际上我通常用你可以在这里找到的很棒的帖子数据库来解决我的所有问题。但我现在实际上被困住了:

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

interface IComUpdate
{
    void Update();
}

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

我的目标是创建一个不同的线程,命名一个 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();

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

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

我的想法是:每次 COM 对象收到更新时,我从 COM 对象线程处理数据,并ModelObj从该线程更新我的实例的某些属性,因此这些属性将引发我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 中的 View 和 ViewModel 之间进行绑定。

所以我无法在此之后追踪base.NotifyOfPropertyChange<string>(() => this.property)。也许 Caliburn 处理线程切换,这不是我的问题。

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

我希望我的 COM 对象线程更新我的ModelObj,它将向 UI 发送一条要更新的消息(因为其中的某些字段ModelObj已更改)并立即继续,而不知道 UI 是否实际更新。

有人知道这种行为吗?

非常感谢你。

####更新####

谢谢大家这么快的回答。

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

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

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

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

所以我宣布它:

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

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

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

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

_NotifyDelegate = new MyDelegate(this.NotifyByInvoke);

然后在我以这种方式使用它之后:

ModelObj.mInvokeControl.BeginInvoke(this._NotifyDelegate );

方法是:

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

一切正常!

4

2 回答 2

3

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

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

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

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

私人委托无效 ExecuteActionHandler(动作动作);

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();
  }
}

还有另一个类似问题的问题,我在那里提供了更多详细信息。

于 2013-03-13T10:29:36.400 回答
0

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

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

于 2013-03-13T11:11:23.093 回答