bradgonesurfing
bradgonesurfing

Reputation: 32162

await UDPClient.ReceiveAsync with timeout

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?

Upvotes: 10

Views: 11280

Answers (5)

TheBigNeo
TheBigNeo

Reputation: 181

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

Upvotes: 2

Simon Mourier
Simon Mourier

Reputation: 138811

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.

Upvotes: 1

gbieging
gbieging

Reputation: 318

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

Upvotes: 3

Hans Passant
Hans Passant

Reputation: 941277

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.

Upvotes: 7

Stephen Cleary
Stephen Cleary

Reputation: 456352

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).

Upvotes: 8

Related Questions