带有 TaskCompletionSource 和 async/await 的 UDP 协议
UDP protocol with TaskCompletionSource and async/await
我有一个使用 UDP 与硬件设备通信的库。
对话是这样的:
|------------000E------------>|
| |
|<-----------000F-------------|
| |
|------------DC23------------>|
| |
|<-----------DC24-------------|
首先我发送操作码 000E 并期望得到 000F 作为响应。收到 000F 后,我会发送一个 DC23 并期望得到一个 DC24 响应。 (响应中包含其他信息以及操作码。)将来,可能需要向此对话添加更多步骤。
负责与设备通信的对象有如下接口:
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);
}
当我尝试天真地编写这段代码时,我最终在 DataArrival
事件处理程序中得到了一个 switch
语句,该语句根据响应代码执行不同的操作,例如:
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;
}
}
它开始看起来像是要变成一个状态机。我认为必须有更好的方法来使用 TaskCompletionSource
和 async/await
.
我该怎么做?
您可以像使用同步 IO 一样编写它,而且通常这比您拥有的基于事件的代码要容易得多。
例如你可以说:
await SendAsync("000E");
var received = await ReceiveAsync();
if (received != "000F") AbortConnection();
await
使得使用同步模式的异步 IO 成为可能。
如果你只是想知道如何在这里使用 TaskCompletionSource
- 你可以这样做,例如:
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
}
然后就可以请求-响应的方式使用了:
var response = await communication.RequestAsync(...);
我有一个使用 UDP 与硬件设备通信的库。 对话是这样的:
|------------000E------------>|
| |
|<-----------000F-------------|
| |
|------------DC23------------>|
| |
|<-----------DC24-------------|
首先我发送操作码 000E 并期望得到 000F 作为响应。收到 000F 后,我会发送一个 DC23 并期望得到一个 DC24 响应。 (响应中包含其他信息以及操作码。)将来,可能需要向此对话添加更多步骤。
负责与设备通信的对象有如下接口:
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);
}
当我尝试天真地编写这段代码时,我最终在 DataArrival
事件处理程序中得到了一个 switch
语句,该语句根据响应代码执行不同的操作,例如:
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;
}
}
它开始看起来像是要变成一个状态机。我认为必须有更好的方法来使用 TaskCompletionSource
和 async/await
.
我该怎么做?
您可以像使用同步 IO 一样编写它,而且通常这比您拥有的基于事件的代码要容易得多。
例如你可以说:
await SendAsync("000E");
var received = await ReceiveAsync();
if (received != "000F") AbortConnection();
await
使得使用同步模式的异步 IO 成为可能。
如果你只是想知道如何在这里使用 TaskCompletionSource
- 你可以这样做,例如:
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
}
然后就可以请求-响应的方式使用了:
var response = await communication.RequestAsync(...);