views:

182

answers:

3

I wrote an application in WPF / VB and separated the business logic and UI into different projects.

The business layer uses a serial port which runs on a different thread, Now that I'm trying to write a command line interface for the same business layer, it seems to fail when .Invoke() is called. (no error, just doesn't work)

I'm pretty sure the reason I had to add in checkaccess and .invoke was because I have collections that would be changed during processing the serial port data and wanted the NotifyCollectionChanged to be handled by WPF data binding. (The reason I'm not 100% sure is because it was months ago I wrote that part and it all worked great from the GUI, now adding the console app has made me rethink some of this)

I would like my business layer to run these processes on the thread they were created, I need this to work from both my GUI version and the command line version.

Am I misusing the Dispatcher in my business layer? Is there a better way to handle an event from the serial port, and then return to the main thread to processes the data?

Updated:

Private Delegate Sub NewDataRecieved(ByVal byteBuffer() As Byte)
Private Sub DataReceived(ByVal byteBuffer() As Byte) Handles _serial.DataRecieved
    If _dispatcher.CheckAccess() Then
        ProcessTheData
    Else
        Dim dataReceivedDelegate As NewDataRecieved
        dataReceivedDelegate = New NewDataRecieved(AddressOf DataReceived)
        _dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, dataReceivedDelegate, byteBuffer)
    End If
End Sub
A: 

WPF in a Console application? In Console applications there are no restrictions on running function in specific thread. You can handle any event in the caller thread context, providing synchronization if necessary.

Alex Farber
The problem is that the business layer is a separate project and I want it to work for both a WPF application, and a console application that references that project... Not WPF in a console application. I have two applications, and one business layer. The hooks I added into the business layer to solve a problem with WPF cause the console app not to work.
zimmer62
+1  A: 

Invoke doesn't do anything because you don't have a dispatcher running, to get any services from a dispatcher you have to call Dispatcher.Run ( http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.run.aspx )

Now, your problem is that calling Dispatcher.Run will make WPF take control of the thread - and that is probably not what you want to do in a console application.

I think the best option in your situation is to remove the thread synchronization code (anything that talks to a dispatcher) from your business layer and into wrapper objects.

The WPF app can continue to work as before by using the wrapper objects and the console app can use the "raw" business layer directly.

Update: here is an example of the wrapper below, you have to create a method/property for every public member of the original class that does the threading work and calls the original object.

public class BOWrapper : INotifyPropertyChanged
{
    private BO _bo;
    private Dispather _dispather;

    public BOWrapper(BO bo, Dispatcher dispather)
    {
        _bo = bo;
        _dispather = dispather;
        _bo.PropertyChanged += BOPropertyChanged;
    }

    public string SomeValue
    {
        get { return _bo.SomeValue; }
    }

    private void BOPropertyChanged(object sender, PropertyChangedEventArgs ea)
    {
        _dispatcher.Invoke(
            new Action<PropertyChangedEventArgs>(
                e=>
                {
                    var handler = PropertyChanged;
                    if(handler!=null) handler(this,e);
                }),ea);
    }
}

The wrapper class is 100% boilerplate code and you probably can use some code generator to create it, maybe even use something like DynamicProxy ( http://www.castleproject.org/dynamicproxy/index.html ) to generate it automatically at runtime.

Nir
I like your explanation as to why invoke doesn't work. I'm trying to figure out what you mean by "wrapper objects" If I create yet another layer that has to be maintained it sort of removes any benefit I had from sharing the one business layer between two applications. Also I'm not quite sure how I'd create this wrapper to handle events from the business layer and then fire more events on the right thread without nearly doubling the amount of code.
zimmer62
I updated the answer, hope this answers your question
Nir
+1. Looks like this has been answered well!
Jeff Wilcox
A: 

An alternative: In the thread where you want a Dispatcher, use the Dispatcher.CurrentDispatcher static property. If a dispatcher has not been created yet for the thread, it is then auto-created (if possible). You could store this value somewhere to allow other threads to use the dispatcher.

In code:

public class BusinessLayerThread
{
    public BusinessLayerThread()
    {
        Dispatcher = Dispatcher.CurrentDispatcher;
    }

    public static Dispatcher Dispatcher { get; private set; }
}
Daniel Rose
This is actually what I was doing, and it's not working. _dispatcher.CheckAccess() returns false, and the code that's supposed to run when _dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, dataReceivedDelegate, byteBuffer) is called never happens.
zimmer62
Try using breakpoints where the dispatcher is stored and where the call is done. Then check that these are actually two different threads.
Daniel Rose