-1

我创建了一个使用可观察列表的应用程序。我已经使 ObservableList 类线程安全(我认为),它现在在我的应用程序中运行良好。

现在我正在尝试将我的应用程序安装为服务。这也很好,直到某些东西被添加到列表中。我认为那里的线程只是死了。我有以下代码:

/// <summary>
/// Creates a new empty ObservableList of the provided type. 
/// </summary>
public ObservableList()
{
    //Assign the current Dispatcher (owner of the collection) 
    _currentDispatcher = Dispatcher.CurrentDispatcher;
}

/// <summary>
/// Executes this action in the right thread
/// </summary>
///<param name="action">The action which should be executed</param>
private void DoDispatchedAction(Action action)
{
    if (_currentDispatcher.CheckAccess())
        action.Invoke();
    else
        _currentDispatcher.Invoke(DispatcherPriority.DataBind, action);
}

/// <summary>
/// Handles the event when a collection has changed.
/// </summary>
/// <param name="e"></param>
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
    DoDispatchedAction(() => base.OnCollectionChanged(e));
}

在调试时,我已经看到Collection.Add(object)被调用了。它启动DoDispatchedAction函数,调试器命中的最后一件事是_currentDispatcher.Invoke(DispatcherPriority.DataBind, action);. 在此之后,应用程序继续,但之后的代码Collection.Add(object)不再执行。最初将项目添加到 ObservableList 的代码也不会继续。这就是为什么我认为线程死了或类似的东西。

在调试器中检查操作时,我发现有以下消息:

ApartmentState = '_currentDispatcher.Thread.ApartmentState' 引发了“System.Threading.ThreadStateException”类型的异常

我怎么解决这个问题?我什至在思考正确的方向吗?

4

2 回答 2

1

据我了解,您有一个应该作为 Windows 服务运行的核心代码,以及一个使用相同核心代码的 WPF 应用程序。

所以基本上你的解决方案中应该有 3 个项目:

  • 完成一些硬件相关工作的核心组件
  • 将作为 Windows 服务安装的可执行文件。此可执行文件引用核心程序集
  • 还引用核心程序集的 WPF 应用程序

调度程序有助于将操作编组回 UI 线程。这基本上用于在 WPF 应用程序的 UI 线程中执行一些代码。例如,当您将集合绑定到 a时,必须在 UI 线程上触发DataGrid该事件,因为由于绑定,它会导致 UI 被更新。CollectionChanged并且 UI必须从 UI 线程更新。

您的核心程序集不必处理调度程序,因为没有要更新的 UI。您可以在此处使用 simple Collection,因为您不会将其绑定到任何 UI 组件。您的 Windows 服务可执行文件也是如此。

另一方面,对于您的 WPF 应用程序,您可以ObservableCollection在 UI 组件上使用绑定(DataGrid例如)。只有在这个程序集中,您必须确保 UI 组件始终从 UI 线程更新(这意味着您需要这样做Dispatcher)。

所以,一个代码示例:

核心组件:

public IEnumerable<SomeClass> GetHardwareInfo()
{
    return new List<SomeClass> { ... };
}

Windows 服务可执行文件:

internal static void Main(string[] args)
{
    ...
    var objs = new MyCoreInstance().GetHardwareInfo();
    ...
}

WPF 应用程序(假设它是 ViewModel):

// Some UI component is binded to this collection that is obersvable
public ObservableCollection<SomeClass> MyCol
{
    get
    {
        return this.myCol;
    }

    set
    {
        if (this.myCol != value)
        {
            this.myCol = value;
            this.RaisePropertyChanged("MyCol");
        }
    }
}

public void UpdateList()
{
    var info = new MyCoreInstance().GetHardwareInfo();

    // Now, marshall back to the UI thread to update the collection
    Application.Current.Dispatcher.Invoke(() =>
        {
            this.MyCol = new ObservableCollection(info);
        });
}
于 2013-04-29T12:24:30.137 回答
1

由于这是一个依赖于硬件的服务,这与通常的 LOB 样式应用程序有点不同。不同之处在于:应该触发事件的更改来自应用程序的后端,而整个 UI 框架和服务架构旨在让前端请求后端提供的数据。

您可以通过在它们相遇的地方创建某种“中立地”来将两者结合在一起。

在硬件处理组件中,我将有一个持续运行或由硬件中断触发运行的后台线程,并使用从硬件收集的任何数据更新其数据结构。然后,我将有一个同步方法,它可以在调用它的时间点创建硬件数据的一致快照。

在 WPF 客户端中,将有一个调度程序计时器,它以设定的时间间隔调用此方法并使用数据快照更新 ObservableCollections。这是可能的,因为它会发生在 UI 线程上。实际上,如果可能的话,您应该尝试在 ObservableCollections 中添加和删除项目,而不是创建新的集合实例,除非集合中的数据从一次调用到下一次调用完全改变。

WCF 客户端只是创建数据快照的方法的包装器:它只会在调用时发回这样的快照。

WCF 服务的 WPF 客户端将作为本地 WPF 客户端工作,只是它会直接调用服务而不是硬件库,并且可能我会为 DispatcherTimer 选择更长的间隔,以避免过多的网络流量。您可以通过返回一个表示“没有任何变化”的特殊代码来进一步优化这一点,以避免多次发送相同的数据,或者使用单独的方法来询问数据是否已更改并检索已更改的数据。

于 2013-05-01T09:07:04.543 回答