0

That title is a little weird, but I have a UdpClient and it uses ReceiveAsync to listen for response from the remote endpoint after a SendAsync. The problem is that the remote endpoint is a very flimsy IoT device that will either close the connection, or never reply.

I want to timeout the ReceiveAsync so the socket can be released when the device decides to do nothing. I saw a genius comment by AJ Richardson here that suggests doing this:

Task.WhenAny(
    client.ReceiveAsync(),
    Task.Delay(5000)
);

I'm sure that works great, but if the ReceiveAsync is successful, I need to read the response, but because it's wrapped in Task.WhenAny, I have no idea how to do that.

Could anyone give me any suggestions? Thanks in advance!

Clarification

To clarify, here's my scenario. I have a Hangfire background job. It receives a data model and based on it sends a message to an IoT device using UDP. The problem is that the devices are flimsy and will not respond, which means the client will be awaiting forever.

If that happens then the client will be holding onto the port, and depending on how many times the job is queued I can eventually run out of ports since their clients are just stuck awaiting.

To avoid that, I want to timeout after a 5 second period and release the client's port and other resources. That is where Task.WhenAny comes in. Either the ReceiveAsync or Task.Delay calls will complete first and end the process.

However, if ReceiveAsync completes first, I need to capture the response from it and do further processing with it. How do I do that?

Here is a more complete code sample of what I'm working with.

var iotAddress = new IPAddress(iot.Ip);
var iotEndpoint = new IPEndPoint(iotAddress, iot.Port);

try {
    using (var client = new UdpClient(0, AddressFamily.InterNetwork)) {
        client.Connect(iotEndpoint);

        await client.SendAsync(bytes, bytes.Length);

        if (!iot.WaitForResponse) {
            return;
        }

        //  await the response of the IoT device
        var response = await client.ReceiveAsync();

        //  OR
        //
        //  await either the response of the IoT device,
        //  or the delay to complete, effectively creating
        //  a timeout.
        var timeoutOrComplete = await Task.WhenAny(
            client.ReceiveAsync(),
            Task.Delay(5000)
        );

        //  If a response was received before the "timeout"
        //  was triggered, how do I get it?
        var response = timeoutOrComplete.???
    }
} catch {
    //  Ignore
}
Gup3rSuR4c
  • 9,145
  • 10
  • 68
  • 126
  • Use the State object in the initialization parameter when you start receiving. Then add receive state into the state object in the receive event. The state object is passed by reference so there is only one instance of the class in the main code and in the event. So changes made in the event will appear in the object in the main thread. – jdweng Sep 18 '19 at 07:19
  • @jdweng, could you post an example of what you mean? I don't think I'm awake enough to put it together on my own right now. Thanks! – Gup3rSuR4c Sep 18 '19 at 15:11
  • See msdn example : https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.udpclient.beginreceive?view=netframework-4.8#System_Net_Sockets_UdpClient_BeginReceive_System_AsyncCallback_System_Object_ – jdweng Sep 18 '19 at 15:49
  • This is for the IAsyncResult type of async operation, but I'm using the await/async methods. I was under the impression they can't be mixed together. – Gup3rSuR4c Sep 18 '19 at 16:08
  • ReceiveAsync is an event that doesn't return anything. All your code is doing is waiting 5 seconds. – jdweng Sep 18 '19 at 16:27
  • @jdweng, I'm not sure were talking about the same things, so I've updated my post to clarify with a sample of what I'm working with. – Gup3rSuR4c Sep 19 '19 at 03:41

1 Answers1

0

Try following :

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        public class UdpState
        {
            public UdpClient u;
            public IPEndPoint e;
            public string receivedMessage;
            // Size of receive buffer.  
            public const int BufferSize = 256;
            // Receive buffer.  
            public byte[] buffer = new byte[BufferSize];  
        }
        public static ManualResetEvent receiveDone = new ManualResetEvent(false);
        public static void Main()
        {
            string ip = "172.0.0.1";
            int port = 11111;

            IPAddress iotAddress =  IPAddress.Parse(ip);
            IPEndPoint iotEndpoint = new IPEndPoint(iotAddress, port);

            byte[] bytes = Encoding.UTF8.GetBytes("Hello World");
            UdpState state = new UdpState();

            try {
                using (UdpClient client = new UdpClient(0, AddressFamily.InterNetwork)) {
                    client.Connect(iotEndpoint);
                    state.e = iotEndpoint;
                    state.u = client;

                    //  await the response of the IoT device
                    client.BeginReceive(new AsyncCallback(ReceiveCallback), state);
                    client.BeginSend(bytes, bytes.Length, iotEndpoint, new AsyncCallback(SendCallback), client); 

                    receiveDone.WaitOne();
                    var response = state.receivedMessage;
                }
            } catch {
                //  Ignore
            }

        }
        public static void ReceiveCallback(IAsyncResult ar)
        {
            UdpState state = ar.AsyncState as UdpState;
            UdpClient u = state.u;
            IPEndPoint e = state.e;

            state.buffer = u.EndReceive(ar, ref e);
            state.receivedMessage = Encoding.ASCII.GetString(state.buffer);

            receiveDone.Set();
         }
        private static void SendCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.  
                UdpClient  client = ar.AsyncState as UdpClient ;

                // Complete sending the data to the remote device.  
                int bytesSent = client.EndSend(ar);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }  
    }
}
jdweng
  • 33,250
  • 2
  • 15
  • 20