Chris Kooken
Chris Kooken

Reputation: 33880

C# async/await for external event handler to fire

I am building an API to communicate with a Sony PTZ camera over UDP. In order to "query" the device (get data from the camera), you have to send a specific UDP packet, then wait for the response to come back. I have a MessageRecieved handler that responds to ANY incoming UDP packet from my camera. The way that I will know it is the "current" response, because the camera sends back the same sequence number I sent it in my request. I am trying to figure out how to do this in an async/await fashion so I can create a single method like GetCurrentAperatureValue

I thought about using something like a concurrent bag to store each command & sequence number that I send, then wait for the server to respond with the same sequence number, and do a lookup in the bag. Maybe poll the bag for the presence of the value? But this doesn't feel right to me.

Here is some abbreviated code to demonstrate what I'm trying to do.

public class SonyAPI {

        public SonyAPI() {
            server = new UDPServer();
            server.MessageReceived += Server_MessageReceived;
            server.Start();
            sequenceNum = 0;
        }

        public async Task<AperatureValue> void GetCurrentAperatureValue(){
             //build the buffer here and send payload;
             server.send("192.168.1.28", 5321, buf);
             
             //STUCK HERE: somehow I need to wait for the MessageRecieved event handler (Below) to fire with my same sequence number that I just sent and then return the result after I process it here. 
             //Becuase this is UDP, there can be lots of messages coming in together. I need to filter out this one that I need. All of this happens in less than 2 ms.
        }


        private void Server_MessageReceived(object sender, UDPMessageEventArgs e) {
              
            var newSequenceNum = BitConverter.ToInt32(e.sequenceNum);                
            Console.WriteLine("message received" + newSequenceNum + " "+ e.RemoteEndPoint);

             //TODO: When the sequence number from the above method call comes back in, send it to the method above so it can return its value.
        }
}

Upvotes: 0

Views: 319

Answers (1)

Andrew Shepherd
Andrew Shepherd

Reputation: 45252

You would create a TaskCompletionSource for each waiting call, and then store this TaskCompletionSource in a lookup.

private readonly ConcurrentDictionary<long, TaskCompletionSource<AperatureValue>> _taskLookup = new ConcurrentDictionary<long, TaskCompletionSource<AperatureValue>>();

public Task<AperatureValue> GetCurrentAperatureValue()
{
    long id = GenerateMessageId();

    var taskCompletionSource = new TaskCompletionSource<AperatureValue>();
    _taskLookup.TryAdd(id, taskCompletionSource);

    //build the buffer here and send payload;
    server.send("192.168.1.28", id, buf);

    return taskCompletionSource.Task;             
}

private void Server_MessageReceived(object sender, UDPMessageEventArgs e) 
{
    var newSequenceNum = BitConverter.ToInt32(e.sequenceNum);               
    if (this._taskLookup.TryRemove(
           newSequenceNum, 
           out TaskCompletionSource<AperatureValue> taskCompletionSource
         )
     )
    {
        taskCompletionSource.SetResult(e.Value);
    }
}

This gives you the basic approach. You would then need to deal with other factors like what happens if a corresponding server message doesn't come back in a reasonable time, and how do you log an error if the received server message does not have a corresponding call.

Upvotes: 1

Related Questions