Threading in a Windows Service

2019-06-14 15:16发布

I've created an app which uses Observable Lists. I've made the ObservableList class threadsafe (I think) and it's working fine now in my application.

Now I'm trying to install my application as a service. This works fine as well, up untill the point something gets added to the list. I think the thread there just dies. I've got the following code:

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

While debugging, I've seen the Collection.Add(object) being called. It starts the DoDispatchedAction function, and the last thing the debugger hits, is _currentDispatcher.Invoke(DispatcherPriority.DataBind, action);. After this, the application continues but the code after Collection.Add(object) doesn't get executed anymore. The code which initially added the item to an ObservableList doesn't continue neither. That's why I think the Thread dies or something like that.

When checking the action in the debugger, I found out that the following message was there:

ApartmentState = '_currentDispatcher.Thread.ApartmentState' threw an exception of type 'System.Threading.ThreadStateException'

How can I solve this problem? Am I even thinking in the right direction?

2条回答
The star\"
2楼-- · 2019-06-14 15:57

From what I understand, you have a core code that should run as a Windows service, and a WPF application that uses the same core code.

So basically you should have something like 3 projects in your solution:

  • a core assembly that does some hardware-related job
  • an executable that will be installed as a Windows service. This executable references the core assembly
  • a WPF application that also references the core assembly

Dispatchers are helpful to marshall back an action to the UI thread. This is basically used to execute some code in the UI thread in WPF applications. For example, when you bind a collection to a DataGrid, the CollectionChanged event must be fired on the UI thread because it'll cause, thanks to the binding, the UI to be updated. And UI must be updated from the UI thread.

Your core assembly shouldn't have to deal with dispatchers as there is no UI to update. You could use simple Collection here, as you won't bind it to any UI component. Same for your Windows service executable.

For your WPF application, on the other hand, you could use an ObservableCollection binded on a UI component (DataGrid for example). Only in this assembly you'll have to ensure UI components are always updated from the UI thread (which means you need the Dispatcher for that).

So, a code example:

Core assembly:

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

Windows Service executable:

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

WPF application (let's say it's the 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);
        });
}
查看更多
够拽才男人
3楼-- · 2019-06-14 15:58

As this is a hardware dependent service, this is a little bit different from the usual LOB-style application. The difference is: the changes which should trigger events come from the backend of the application, while the whole UI framework and service architecture is intended to be used so that the frontend asks for data which the backend provides.

You could bring the two together by creating some sort of "neutral ground" where they meet.

In the hardware handling component, I would have a background thread which runs continually or runs triggered by hardware interrupts, and updates its data structures with whatever data it collects from the hardware. Then, I would have a synchronized method which can create a consistent snapshot of the hardware data at the point of time when it is called.

In the WPF client, there would be a dispatcher timer which calls this method in set intervals and updates the ObservableCollections using the data snapshots. This is possible, because it would happen on the UI thread. Actually, if possible you should try to add and remove items from the ObservableCollections, not create new collection instances, unless the data in the collection changes completely from one call to the next.

The WCF client would only be a wrapper around the method which creates data snapshots: it would only send back such a snapshot when it is called.

The WPF client for the WCF service would work as the local WPF client, only it would call the service instead of the hardware library directly, and probably I'd choose a longer interval for the DispatcherTimer, in order to avoid excessive network traffic. You could further optimize this by returning a special code which means "nothing has changed", in order to avoid sending the same data several times, or have separate methods for asking whether data has changed and retrieving the changed data.

查看更多
登录 后发表回答