UDP协议与TaskCompletionSource和async / await

时间:2017-07-03 13:46:59

标签: c# asynchronous taskcompletionsource

我有一个使用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);
}

当我尝试天真地编写这段代码时,我最终在switch事件处理程序中使用DataArrival语句根据响应代码执行不同的操作,如下所示:

    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;
        }
    }

它开始看起来会变成状态机。我认为必须有更好的方法来使用TaskCompletionSourceasync/await

我该怎么做?

2 个答案:

答案 0 :(得分:1)

您可以像使用同步IO一样编写它,并且通常比您拥有的基于事件的代码更容易。

例如,您可以说:

await SendAsync("000E");
var received = await ReceiveAsync();
if (received != "000F") AbortConnection();

await可以将异步IO与同步模式一起使用。

答案 1 :(得分:1)

如果你只是想知道如何在这里使用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(...);