I have a library that talks to a hardware device using UDP. The conversation goes something like this:
|------------000E------------>|
| |
|<-----------000F-------------|
| |
|------------DC23------------>|
| |
|<-----------DC24-------------|
First I send out opcode 000E and expect to get a 000F in response. Once I get the 000F, I send out a DC23 and expect a DC24 in response. (There is additional information included in the response along with the opcode.) In the future, more steps may need to be added to this conversation.
The object in charge of communicating with the device has the following interface:
public class Communication : ICommunication
{
public Communication();
public bool Send_LAN(byte subnetID, byte deviceID, int operateCode, ref byte[] addtional);
public event DataArrivalHandler DataArrival;
public delegate void DataArrivalHandler(byte subnetID, byte deviceID, int deviceType, int operateCode, int lengthOfAddtional, ref byte[] addtional);
}
When I try to write this code naively I end up with a switch
statement in the DataArrival
event handler that does different things according to the response code, like so:
private void _com_DataArrival(byte subnetID, byte deviceID, int deviceTypeCode, int operateCode, int lengthOfAddtional, ref byte[] addtional)
{
Debug.WriteLine($"OpCode: 0x{operateCode:X4}");
switch (operateCode)
{
case 0x000F: // Response to scan
// Process the response...
_com.Send_LAN(subnet, device, 0xDC23, ...);
break;
case 0xDC24:
// Continue processing...
break;
}
}
It's beginning to look like it's going to turn into a state machine. I think there has to be a better way to do it using TaskCompletionSource
and async/await
.
How do I go about doing this?
If you just want to know how to use TaskCompletionSource
here - you can do it for example like this:
public Task<Response> RequestAsync(byte subnetID, byte deviceID, int deviceType, int operateCode, ref byte[] addtional, int expectedResponseCode, CancellationToken ct = default(CancellationToken)) {
var tcs = new TaskCompletionSource<Response>();
DataArrivalHandler handler = null;
handler = (byte sub, byte device, int type, int opCode, int length, ref byte[] additional) => {
// got something, check if that is what we are waiting for
if (opCode == expectedResponseCode) {
DataArrival -= handler;
// construct response here
Response res = null; // = new Response(subnetID, deviceID, etc)
tcs.TrySetResult(res);
}
};
DataArrival += handler;
// you can use cancellation for timeouts also
ct.Register(() =>
{
DataArrival -= handler;
tcs.TrySetCanceled(ct);
});
if (!Send_LAN(subnetID, deviceID, operateCode, ref addtional)) {
DataArrival -= handler;
// throw here, or set exception on task completion source, or set result to null
tcs.TrySetException(new Exception("Send_LAN returned false"));
}
return tcs.Task;
}
public class Response {
public byte SubnetID { get; set; }
// etc
}
Then you can use it in request-response manner:
var response = await communication.RequestAsync(...);