await UDPClient.ReceiveAsync with timeout
Asked Answered
I

5

10

I'm using UDPClient like below

dim c = New UDPClient(port)
client.CLient.ReceiveTimeout = 1
await client.ReceiveAsync()

However the await does not terminate or throw even though I have set a timeout. Is this normal behaviour?

Instrument answered 28/9, 2012 at 10:19 Comment(0)
U
7

It is explicitly mentioned in the MSDN Library article for Socket.ReceiveTimeout:

Gets or sets a value that specifies the amount of time after which a synchronous Receive call will time out.

Emphasis added. You are doing the opposite of a synchronous receive when you use ReceiveAsync(). The workaround is to use a System.Timers.Timer that you start before the call and stop afterwards. Close the socket in the Elapsed event handler so the ReceiveAsync() method terminates with an ObjectDisposed exception.

Ubangi answered 28/9, 2012 at 14:51 Comment(2)
Good idea. are there any thread safety issues closing the socket like this whilst another thread is waiting on it?Instrument
I cannot possibly answer that without any decent info. Getting multiple threads to read from a socket asynchronously is however never a problem, it is impossible.Ubangi
F
8

Yes. The asynchronous methods on Socket do not implement the timeouts. If you need timeouts on asynchronous operations, you have to create them yourself (e.g., using Task.Delay and Task.WhenAny).

Flatfish answered 28/9, 2012 at 12:16 Comment(2)
An example would improve this answer.Embryo
var firstTaskToComplete = await Task.WhenAny(udpClient.ReceiveAsync(), Task.Delay(milliseconds))Hewett
U
7

It is explicitly mentioned in the MSDN Library article for Socket.ReceiveTimeout:

Gets or sets a value that specifies the amount of time after which a synchronous Receive call will time out.

Emphasis added. You are doing the opposite of a synchronous receive when you use ReceiveAsync(). The workaround is to use a System.Timers.Timer that you start before the call and stop afterwards. Close the socket in the Elapsed event handler so the ReceiveAsync() method terminates with an ObjectDisposed exception.

Ubangi answered 28/9, 2012 at 14:51 Comment(2)
Good idea. are there any thread safety issues closing the socket like this whilst another thread is waiting on it?Instrument
I cannot possibly answer that without any decent info. Getting multiple threads to read from a socket asynchronously is however never a problem, it is impossible.Ubangi
G
3

I had this issue recently and this is how I solved it:

async Task Listen(IPEndPoint ep, int timeout)
{
    using (var udp = new UdpClient(ep))
    {
        var result = await Task.Run(() =>
        {
            var task = udp.ReceiveAsync();
            task.Wait(timeout);
            if (task.IsCompleted)
            { return task.Result; }
            throw new TimeoutException();
        });

        Receive(result); // use the result
    }
}
Giorgio answered 30/11, 2017 at 16:5 Comment(3)
Note, that if the timeout is hit the read is still running and will eventually drop the data it receives. You can only use this technique if you shut down the whole socket in case of a timeout.Electroluminescence
@Electroluminescence Won't the socket be closed once the udp client falls out of scope since it is in a using clause?Sheng
Not a very good solution. Effectively using a thread pool thread to do a synchronous blocking Wait(). If you are going to block a thread, you could just call Receive() directly (and use Socket.ReceiveTimeout for the timeout).Vibratory
T
2

You can also give the ReceiveAsync function a CancellationToken and let this token expire after the timeout.

private async Task ReceiveDataWithoutTokenAsync(int timeoutInMs)
{
    using (CancellationTokenSource cancellationTokenSource = new())
    {
        cancellationTokenSource.CancelAfter(timeoutInMs);
        await UdpClient.ReceiveAsync(cancellationTokenSource.Token);
    }
}

Or, if you already have a token, you can link the external token and the timeout token.

private async Task ReceiveDataWithExternalTokenAsync(int timeoutInMs, CancellationToken externalToken)
{
    using (CancellationTokenSource internalCancellationTokenSource = new())
    {
        using (CancellationTokenSource linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(internalCancellationTokenSource.Token, externalToken))
        {
            internalCancellationTokenSource.CancelAfter(timeoutInMs);
            await UdpClient.ReceiveAsync(linkedTokenSource.Token);
        }
    }
}

According to How to: Listen for Multiple Cancellation Requests

Tutty answered 20/2, 2024 at 14:40 Comment(2)
Worth noting, this overload is available from .NET 9 onward: learn.microsoft.com/en-us/dotnet/api/…Hewett
@MickaelBergeronNéron I think you misread that link. Behind "and other versions" hides .NET 6+.Vibratory
E
1

For what it's worth, this is how I do it (also with the possible combination of a cancellation token):

public static async Task<byte[]> SendReceiveUdpAsync(IPEndPoint endPoint, byte[] packet, int timeout, CancellationToken cancellationToken)
{
    using var client = new UdpClient(endPoint.AddressFamily);
    await client.SendAsync(packet, endPoint, cancellationToken).ConfigureAwait(false);
    var task = client.ReceiveAsync(cancellationToken);
    var index = Task.WaitAny(new [] { task.AsTask() }, timeout, cancellationToken);
    if (index < 0)
        return null;

    return task.Result.Buffer;
}

The trick is to wait for the UDP receive task in a Task.WaitAny call.

Editorialize answered 9/12, 2022 at 19:29 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.