Link to home
Start Free TrialLog in
Avatar of deleyd
deleydFlag for United States of America

asked on

notify with a status?

I know you can notify with
event.WaitOne();

Open in new window

What if my receiver wants to know a little more than just a ping? I can be notified that a message has arrived. What if I also want to be notified with an integer that summarizes the message?

I thought of leaving the integer in a global place for receiver to fetch, but there's no promise the number they fetch hasn't been written over by the time they fetch it. I'd like a little more direct delivery to the receiver.
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Wrap the thread, ping, and Wait() in a CLASS so that you can create multiple instances and store data about each one individually.
Avatar of deleyd

ASKER

Not quite sure what is meant. I send a message and expect a reply. Simple in Procedural oriented language: send message, read reply.  A bit harder in Event-Driven language. I send a message. Someone else reads replies. I need to know if reply is what we expected or if there was a problem.

1. a sender that sends a message and wants to know if the reply that comes in is the expected reply

2. a watcher waiting for a message to arrive

3. watcher is notified of event "incoming reply has arrived

4. watcher retrieves incoming reply

5. watcher parses reply

6. watcher would now like to notify someone about this reply to their message, so they can determine if this is the expected reply or if there was a problem

I have no idea what you're doing so here's a made-up example: Idle-Mind-512432.flv
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private delegate void ProcessReply(string reply);

        private void button1_Click(object sender, EventArgs e)
        {
            if (this.textBox1.Text.Trim().Length > 0)
            {
                SomeDevice sd = new SomeDevice();
                sd.Reply += new SomeDevice.ReplySignature(sd_Reply);
                sd.SendMessage(textBox1.Text);
                textBox1.Clear();
                textBox1.Focus();
            }
        }

        void sd_Reply(string reply)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new ProcessReply(sd_Reply), new Object[] { reply });
            }
            else
            {
                listBox1.Items.Add(reply);
            }
        }

    }

    public class SomeDevice
    {

        private string message;
        private static Random R = new Random();
        private System.Threading.Thread T = null;
        private System.Threading.ManualResetEvent mre = new System.Threading.ManualResetEvent(false);
        
        public delegate void ReplySignature(string reply);
        public event ReplySignature Reply;

        public void SendMessage(string message)
        {
            if (T == null)
            {
                this.message = message;
                this.mre.Reset();
                this.T = new System.Threading.Thread(new System.Threading.ThreadStart(this.WaitForReply));
                this.T.Start();

                System.Threading.Thread Respond = new System.Threading.Thread(new System.Threading.ThreadStart(this.RandomResponseTime));
                Respond.Start();
            }
        }

        private void WaitForReply()
        {
            this.mre.WaitOne();

            if (this.Reply != null)
            {
                // Reply with the initial message backwards
                string reply = "";
                for (int i = this.message.Length - 1; i >= 0; i--)
                {
                    reply = reply + message.Substring(i, 1);
                }

                this.Reply(reply);
            }

            this.T = null;
        }

        private void RandomResponseTime()
        {
            System.Threading.Thread.Sleep(SomeDevice.R.Next(3000, 10001)); // three to ten second response time
            mre.Set();
        }

    }

}

Open in new window

Avatar of deleyd

ASKER

So SomeDevice sends a message with sd.SendMessage, then we start a thread which waits for a reply, and when we get a reply we display it.

Where would we insert a check to make sure the reply matches up with what was sent? (i.e. the reply is what we expected and not an error message. If it is an error message, how do we trace that error message back to the message we sent which generated that error message reply?)

Where would we insert a timeout in case we never get a reply?
Good questions.

I would modify the event signature so that it also passes out a reference to the instance of SomeDevice that is the source of the event:

    public delegate void ReplySignature(SomeDevice sender, string reply);

Then, when you raise the event, you pass out "this" as the first parameter:

    this.Reply(this, reply);

Back in the Form, we'll have a reference to the instance of SomeDevice:

        private delegate void ProcessReply(SomeDevice sender, string reply);

        void sd_Reply(SomeDevice sender, string reply)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new ProcessReply(sd_Reply), new Object[] { sender, reply });
            }
            else
            {
               // ... here we can use "sender" to make decisions ...
                if (sender.VVV == WWW && sender.XXX = YYY)
                {

                }

                listBox1.Items.Add(reply);
            }
        }

You'll need to publicly expose the properties in SomeDevice that you want to get to.  In my example "message" was private so you can't get to it.  You'd probably want to make that a public readonly property.  

Additionally, you could store the "reply" in SomeDevice as a public readonly property too.

The "timeout" feature can be implemented in SomeDevice as well by starting another thread that simply sleeps for the timeout duration and then cancels the "send" thread with Abort(); or uses a more elegant mechanism with WaitAll().  You can make the SomeDevice class raise a different custom TimedOut() event and also create another public readonly property to indicate success/failure.
Avatar of deleyd

ASKER

Thank you very good fun and educational to play with.

Device IO in use flag
Now the last thing I want to add is some sort of a lock flag that indicates "Device IO in use" so that no one can send a 2nd message to the device until the device responds to the 1st message and I am satisfied with the response.

Because this device is temperamental. If I send a 2nd message to it before it replies to the 1st message, it gets all upset and hangs.

Also, there's one case where the device will reply twice, first with a NAK reply, then 20 seconds later with its own "timeout" reply.

So if I get a NAK reply to a certain command I need to prevent other messages from being sent until we get that "timeout" reply, or again the device will hang.



Device got unplugged
And again I'll later need to throw in my own timers to deal with what to do if we don't hear back from the device after a reasonable amount of time (device came unplugged), we then need to initiate recovery procedure to reestablish communication with device:

1)Tell use to plug in the device

2)close port which will throw an exception complaining the port isn't open, but I have to do this because if I simply try to open the port I'll get an exception complaining the port is already open

3) search COM ports for device. (Currently a loop with a pause. Fancier would be to get notified when device gets plugged in.)

3) open port

4) send a test ping to device to confirm we have reestablished communication

5) resume whatever I was doing
(This in itself could be a bit of work to determine what was I doing before this interruption. With procedural code my "state" is determined by where I am in the code, but with this event driven paradigm I'm not sure, maybe I need to set a bunch of global parameters that keep track of what "state" I'm in, what I've already done and what I still need to do.)
ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
For the timeouts, previously I suggested using another thread that sleeps and then cancels the other thread.  That is still a viable approach...though some might say it is not elegant to kill another thread.

A different approach is to pass a time interval to the WaitOne() call that waits for the reply:
http://msdn.microsoft.com/en-us/library/aa332440(VS.71).aspx

When a reply is received you can set a flag to indicate as such.  Then, when the WaitOne() call returns, you can check the flag to determine if a reply was actually received or if it returned because the timeout duration was reached.
Avatar of deleyd

ASKER

Thank you very much for all the knowledge. I didn't know one could put the monitor.Exit in another method. I thought monitor got wrapped around a code block and too bad if that's not when you want to release the lock.

It's brought up a number of little nuance questions I'll start another thread on.
It gets complicated...I think the Exit() needs to be running in the same thread as the Enter().  So you could start in one method and then jump to another (you'd be in the same thread still), or if an event is running in the same thread that would work.  Have to really know the flow of the app and where things come from.

That's why I posted the link to the other thread synchronization techniques so you can see your other options and pick the one(s) that fit your needs best.  =)