0

I have a class that talks to a serial port and I want to mimic a Get function. The problem is, the serial port feeds in raw data which may or may not come. It also has to be processed and parsed. For example, if I want to get a Person object, the serial port class does this.

  1. A method to send the request for a Person.

  2. Something happens on the serial device... it might respond, it might not.

  3. Bytes come in through an event handler that processes and parses all data, and eventually a Person object.

I want to wrap that all up into a "GetPerson" function but I'm having difficulty connecting steps in 1 and 3. Step 3 is a function that is always running and is more of a generic parser.

Can someone help me design/structure this?

Edit: Also I've tried using a class variable and checking via while statement as shown below but it doesn't check the variable during the while statement for whatever reason:

private Person person;

public Person GetPerson()
{
   ...
   while (person == null) {}
   return person;
}
Tong
  • 697
  • 1
  • 8
  • 16

1 Answers1

0

As far as I understood you need to convert EAP to TAP. In other words to wait for some data receiving event in async\await manner. You can use TaskComplettionSource<> for that. Based on this article I tried to imagine the design appropriate solution:

public class PersonSerialPort : IDisposable
{
    private readonly SerialPort port;

    /// <summary>
    /// Timeout in milliseconds
    /// </summary>
    private const int Timeout = 5000;

    public PersonSerialPort()
    {
        // port initializing here
        port = new SerialPort(/*your parameters here*/);
        port.Open();
    }

    public async Task<Person> GetPerson()
    {
        // set up the task completion source
        var tcs = new TaskCompletionSource<Person>();

        // handler of DataReceived event of port
        var handler = default(SerialDataReceivedEventHandler);
        handler = (sender, eventArgs) =>
        {
            try
            {
                Person result = new Person();

                // some logic for filling Person fields
                // or set it null or whatever you need
                // you are free to not set result and wait for next event fired too

                tcs.SetResult(result);

            }
            finally
            {
                port.DataReceived -= handler;
            }
        };
        port.DataReceived += handler;

        // send request for person
        port.Write("Give a person number 1");

        if (await Task.WhenAny(tcs.Task, Task.Delay(Timeout)) == tcs.Task)
        {
            return tcs.Task.Result;
        }
        else
        {
            port.DataReceived -= handler;
            throw new TimeoutException("Timeout has expired");
        }
    }

    public void Dispose()
    {
        port?.Dispose();
    }
}

UPD: timeout logic is added, see also this question.

Hope it helps.

KozhevnikovDmitry
  • 1,660
  • 12
  • 27
  • Thanks for the terminology, it should actually help me out with looking it up. I'll try to understand it before trying out your code out. – Tong Sep 06 '18 at 17:03
  • Ok, this looks good so far. I guess my last question regarding this is how to timeout the handler. I've read up on CancellationTokens but they only apply to Tasks, not delegates. – Tong Sep 06 '18 at 23:39
  • I have added the timeout logic. You see, it can be achieved with second task and `Task.WhenAny()` approach. Don't forget to unsubscribe of `DataReceived` event. – KozhevnikovDmitry Sep 07 '18 at 01:24
  • This really opened a new window for me. Thank you! – Tong Sep 07 '18 at 03:45
  • Happy to help=) – KozhevnikovDmitry Sep 07 '18 at 03:47