0

I am writing a wpf application to control an embedded device over TCP. Writing to the device is easy, but I am having trouble receiving packets.

I have created a tcp NetworkStream and am using NetworkStream.BeginRead() to listen for TCP data. However, once I have composed a full packet, I would like to update my GUI to reflect the new information. It seems that I am not allowed to do this from the asynchronous callback thread.

It appears that there is a way to do this through the dispatcher one request at a time, but I need to update practically every control on my GUI. I am not writing a dispatcher function for every packet case and every WPF control. How do I get complete access to the WPF controls from my asynchronous thread?

EDIT: Here is a code sample:

NetworkUnion union = (NetworkUnion)ar.AsyncState;
                union.BytesRead += tcpStream.EndRead(ar);

                if (union.BytesRead < union.TargetSize)
                    tcpStream.BeginRead(union.ByteArray, union.BytesRead, union.TargetSize - union.BytesRead, new AsyncCallback(ReadCommandCallback), union);
                else
                {
                    NetworkUnion payload = new NetworkUnion();
                    NetworkPacket pkt = (NetworkPacket)union.getArrayAsStruct();

                    // Respond to the packet
                    // Read the payload
                    payload.ByteArray = new byte[pkt.payloadSize];
                    tcpStream.Read(payload.ByteArray, 0, pkt.payloadSize);

                    // Determine what the payload is!
                    switch (pkt.code)
                    {
                        case (int)PacketCode.STATE:
                            payload.ObjectType = typeof(NetworkState);
                            NetworkState state = (NetworkState)payload.getArrayAsStruct();
                            Handle.fuelGauge.Value = Convert.ToDouble(state.mainFuel);
                            break;

When I try to update the fuel gauge, I get an InvalidOperationException, calling thread cannot access because another thread owns the object

The 'Handle' variable is used because this is from within a static utility class

The main question is, do I really have to replace every line of

Handle.fuelGauge.Value = Convert.ToDouble(state.mainFuel);

with

Handle.fuelGauge.Dispatcher.Invoke(
                                System.Windows.Threading.DispatcherPriority.Normal,
                                new Action(
                                    delegate()
                                    {
                                        Handle.fuelGauge.Value = Convert.ToDouble(state.mainFuel);
                                    }
                            ));

? It seems excessively repetitive. It would be easier to do something like:

Handle.mainWindow.Dispather.Lock();
Handle.fuelGauge.Value = Convert.ToDouble(state.mainFuel);
change everything else...
Handle.mainWindow.Dispatcher.Unlock();
8
  • Have you tried using invoke or beinginvoke on the control you wish to update? Commented Jun 1, 2012 at 21:01
  • What have you tried? The problem that you mention does not exist in practice. Therefore, my guess is that you didn't even try. Commented Jun 1, 2012 at 21:02
  • Looking into it now. Could you please provide a link or example? I'm still pretty new to this multithreaded thing :-) Commented Jun 1, 2012 at 21:03
  • @usr: I have added a code sample to my question Commented Jun 1, 2012 at 21:06
  • 1
    @AGuyInAPlace Then it would seem the method your using to create the packet is not generic enough? Are you saying you have 50+ unique method calls? If you have one or two generic method, you can use the same invoker to call back to the control. Commented Jun 1, 2012 at 21:19

2 Answers 2

1

Generally, in .NET, you need to marshal UI requests back to the main UI thread. This is a common scenario when trying to update UI after a network read, because when using Sockets, you are usually always receiving data on some other (threadpool) thread.

So, in your code which receives the data, you should wrap up the bytes into some useful object, and marshal that back to an entry point method on the main UI thread which can then take responsibility for farming out UI updates, on the now correct thread.

There are many ways to do this, but perhaps a more flexible approach than some, would be for your callers of the socket code to pass in a SynchronizationContext object, which can then be used to marshal the received data to.

See here for more info on SynchronizationContext

You won't need to post back every socket call, just move your swithc statement, that decides how to handle a packet, into a class on the UI thread, and invoke to that method. Then, that method can do UI updates as normal, as all calls from it are already marshalled correctly.

Sign up to request clarification or add additional context in comments.

5 Comments

Aha! This sounds like exactly what I'm trying to do. Give me a few to play around with SynchronizationContext...
Searching for information about SynchronizationContext brought me to this thread: (stackoverflow.com/questions/1949789/…). As per Ray's advice, I am using a single dispatcher call to pass my packet to a method inside the UI thread, where the packet is received and the UI is updated as desired. However, after an arbitrary number of packets, I receive System.Reflection.TargetInvocationException. I can't catch it because it is thrown from outside my code. My best guess is some sort of race condition? What now?
Look at the .InnerException property of that exception to get more info about what has happened. Likely, there is a problem in your UI code, and this bubbles up through to the SynchronizationContext.Post call (and is wrapped in a TargetInvocationException). Also, add some exception handling in the source of your .Post callback to make sure the exception doesn't break your socket handling.
That's the strange part; I can't seem to catch this exception anywhere. I wrapped the entire socket thread in try/catch, as well as the UI code. Without catching it, I can't examine the inner data
Aha! Found it! One of the variables in my switch statement was being used uninitialized. Fixed it up, and everything works like a top! Thanks!
0

No you dont need to replace every line like you said. How about something like this:

NetworkUnion union = (NetworkUnion)ar.AsyncState;
            union.BytesRead += tcpStream.EndRead(ar);

            if (union.BytesRead < union.TargetSize)
                tcpStream.BeginRead(union.ByteArray, union.BytesRead, union.TargetSize - union.BytesRead, new AsyncCallback(ReadCommandCallback), union);
            else
            {
                NetworkUnion payload = new NetworkUnion();
                NetworkPacket pkt = (NetworkPacket)union.getArrayAsStruct();

                // Respond to the packet
                // Read the payload
                payload.ByteArray = new byte[pkt.payloadSize];
                tcpStream.Read(payload.ByteArray, 0, pkt.payloadSize);

                // Determine what the payload is!
                double yourvalue = new double(); 
                switch (pkt.code)
                {
                    case (int)PacketCode.STATE:
                        payload.ObjectType = typeof(NetworkState);
                        NetworkState state = (NetworkState)payload.getArrayAsStruct();
                        yourvalue = Convert.ToDouble(state.mainFuel);
                        break;
            /**
                Other Cases....

            */
         // After the switch 

                    Handle.fuelGauge.Dispatcher.Invoke(
                            System.Windows.Threading.DispatcherPriority.Normal,
                            new Action(
                                delegate()
                                {
                                    Handle.fuelGauge.Value = yourvalue;
                                }
                        )); 

1 Comment

This is a better option, but still requires yourvalue00 through yourvalue52 to be previously declared. Lohan's solution seems more appropriate.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.