So what am I supposed to do?
In this particular case, I would rather use UdpClient.Client.ReceiveTimeout
and TcpClient.ReceiveTimeout
to time out a UDP or TCP receive operation gracefully. I'd like to have the time-out error coming from the socket, rather than from any external source.
If in addition to that I need to observe some other cancellation event, like a UI button click, I would just use WithCancellation
from Stephen Toub's "How do I cancel non-cancelable async operations?", like this:
using (var client = new UdpClient())
{
UdpClient.Client.ReceiveTimeout = 2000;
var result = await client.ReceiveAsync().WithCancellation(userToken);
// ...
}
To address the comment, in case ReceiveTimeout
has no effect on ReceiveAsync
, I'd still use WithCancellation
:
using (var client = new UdpClient())
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(userToken))
{
UdpClient.Client.ReceiveTimeout = 2000;
cts.CancelAfter(2000);
var result = await client.ReceiveAsync().WithCancellation(cts.Token);
// ...
}
IMO, this more clearly shows my intentions as a developer and is more readable to a 3rd party. Also, I don't need to catch ObjectDisposedException
exeception. I still need to observe OperationCanceledException
somewhere in my client code which calls this, but I'd be doing that anyway. OperationCanceledException
usually stands out from other exceptions, and I have an option to check OperationCanceledException.CancellationToken
to observe the reason for cancellation.
Other than that, there's not much difference from @I3arnon's answer. I just don't feel like I need another pattern for this, as I already have WithCancellation
at my disposal.
To further address the comments:
- I'd only be catching
OperationCanceledException
in the client code, i.e.:
async void Button_Click(sender o, EventArgs args)
{
try
{
await DoSocketStuffAsync(_userCancellationToken.Token);
}
catch (Exception ex)
{
while (ex is AggregateException)
ex = ex.InnerException;
if (ex is OperationCanceledException)
return; // ignore if cancelled
// report otherwise
MessageBox.Show(ex.Message);
}
}
- Yes, I'll be using
WithCancellation
with each ReadAsync
call and I like that fact, for the following reasons. Firstly, I can create an extension ReceiveAsyncWithToken
:
public static class UdpClientExt
{
public static Task<UdpReceiveResult> ReceiveAsyncWithToken(
this UdpClient client, CancellationToken token)
{
return client.ReceiveAsync().WithCancellation(token);
}
}
Secondly, in 3yrs from now I may be reviewing this code for .NET 6.0. By then, Microsoft may have a new API, UdpClient.ReceiveAsyncWithTimeout
. In my case, I'll simply replace ReceiveAsyncWithToken(token)
or ReceiveAsync().WithCancellation(token)
with ReceiveAsyncWithTimeout(timeout, userToken)
. It would not be so obvious to deal with CreateTimeoutScope
.