Read event argument after wait

433 views Asked by At

I have a hardware component that I use with TCP communication. The class that control it has 3 jobs: Trigger the device, listen to incoming messages and raise an event when a message is received:

public class Hardware
{
    public event Action<string> OnHardwareMessage;
    private NetworkStream stream = new NetworkStream();
    public Hardware()
    {
        Task.Factory.StartNew(() => { Listen(); });
    }

    private void Listen()
    {
        //listen to TCP port and raise an event when a message is received
        byte[] bytes = new byte[1024];
        int bytesRead = stream.Read(bytes, 0, bytes.Length);

        string response = Encoding.ASCII.GetString(bytes, 0, bytesRead);

        if (OnHardwareMessage != null)
            OnHardwareMessage(response);
    }

    public void Trigger()
    {
        //Trigger the hardware component
        //the response usually takes up to 5 seconds to arrive
    }
}

This class is used in a loop inside a view-model:

public class MainViewModel
{
    private static EventWaitHandle hardwareWaiter =
        new EventWaitHandle(false, EventResetMode.AutoReset);

    private Hardware hardware = new Hardware();

    //what i'm doing now is holding a field for incoming event results
    private string hardwareResult;

    public MainViewModel()
    {
        hardware.OnHardwareMessage += hardware_OnHardwareMessage;

        while (true)
        {
            hardware.Trigger();
            if (hardwareWaiter.WaitOne(10000))
            {
                //is it possible to read the event argument here?

                //do something with the event argument
                someObservableCollection.Add(hardwareResult);

                //clear the value
                hardwareResult = string.Empty;
            }
            else
            {
                throw new Exception("Hardware did not respond on time");
            }
        }
    }

    //event listener for hardware events
    private void hardware_OnHardwareMessage(string message)
    {
        hardwareResult = message;
        hardwareWaiter.Set();
    }
}

What I do is trigger the device and wait for up to 10 seconds for a response. What I'm doing now is holding a class scoped field, assign the message received inside the event listener, read it in the inside the loop and clear it.
My question is if there's any built in mechanism that could let me read the event argument directly after the EventWaitHandle was signaled (outside the event listener).

1

There are 1 answers

0
usr On

I would not base this on events. Maybe your listener should expose a BlockingCollection that represents the incoming stream of messages. Readers can then take from that collection with a timeout. If the timeout hits no message is taken and lost (this is a race condition that you have right now).

If you insist on using events use a closure to thread the message state:

string hardwareResult = null;
Action handler = (...) =>
    {
        hardwareResult = message;
        hardwareWaiter.Set();
    };

    hardware.OnHardwareMessage += handler;

That way you only need a local variable which is scoped more tightly than a field.

There's the usual TCP bug as well: You assume to read an entire message. You could read one byte at a time instead.