Domanda

I'm communicating with a device that's connected to the computer via com port. The device accepts certain predefined commands in order to interact with it. I'm essentially creating a more abstract API on top of the one provided by the device.

I encapsulated the charactersistics of the Commands I use, so that I can just throw those objects at my Connection class, which handles the com port.

connection.Send(new FooCommand());

connection then

  • shoves the ICommand into an internally used SerialPort object
  • listens for SerialPort.DataReceived for the expected result
  • sends the next ICommand (if there is one)

So far so good.


For some ICommand, the device returns meaningful values (and does not simply acknowledge the reception). Those can be handled in optional Action<> callbacks, that are passed to the constructor.

connection.Send(new BarCommand(response => Console.WriteLine(response)));

This works ok to just get a value, but becomes complicated for more complex communication that invoves multiple instructions being sent that depend on each other. To illustrate, let's assume the returned value of one command might be necessary to send another one.

The trivial solution is to put the second call into the response callback of the first one.

connection.Send(new BarCommand(response => connection.Send(new BazCommand(response)));

This quickly becomes messy. A more readable solution could be to store the response in a variable.

var baz = new BazCommand();
connection.Send(new BarCommand(response => baz.Parameter = response));
connection.Send(baz)

Now however, BazCommand has to be mutable, because it will be completed (with the necessary value for its parameter property) only after it is passed to the connection.Send() method. It also means that connection.Send() has to evaluate its parameter lazily.

Even in this simple example, the order of operations is a bit obscured already.

It gets worse if sending the BazCommand at all should be dependent on the response. Lambdas are not well suited for such branching code. A method can be used.

connection.Send(new BarCommand(responseHandler));

private void responseHandler(bool response) {
    if(response) connection.Send(baz)

This leads to dozens of methods like responseHandler that essentially handle the different state transitions of my object (not a bad thing in itself), which bloats the code and reduces readability.


I'm not too experienced with C# and need some guidance how to improve the communication code. Is this a use case for async/await? Would it allow me to write code like the following?

bool response = connection.Send(new BarCommand);
/* stop here until response is actually set to a value comming from the device */
connection.Send(new BazCommand(response))

or

bool response = connection.Send(new BarCommand);
/* stop here until response is actually set to a value comming from the device */
if (response) connection.Send(new BazCommand())
È stato utile?

Soluzione

I'm not too experienced with C# and need some guidance how to improve the communication code. Is this a use case for async/await? Would it allow me to write code like the following?

This is indeed a very applicable scenario for using async/await.

public static class CommandHelper
{
    public static async Task<T> SendAsync<T>(this SerialConnection connection, ICommand command)
    {
        var tcs = new TaskCompletionSource<T>();
        command.DataReceived += (sender, data) => tcs.SetResult((T)data);
        connection.Send(command);
        return await tcs.Task;
    }
}

With this you'll be able to 'await' every serial call and chain them together like so

var welcomeResponse = await connection.SendAsync<WelcomeResponse>(new WelcomeCommand());
var registrationReply = await connection.SendAsync<RegistrationReply>(new RegistrationCommand(welcomeResponse.Name));

You get the idea. Async/await really cleans up these scenarios where you'd otherwise wait for data using an event handler.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
scroll top