Kru*_*lur 9 c# asynchronous async-await
我有INetwork一个方法的接口:
Task<bool> SendAsync(string messageToSend, CancellationToken ct)
Run Code Online (Sandbox Code Playgroud)
接口的一个实现具有如下代码:
public async Task<bool> SendAsync(string messageToSend, CancellationToken ct)
{
var udpClient = new UdpClient();
var data = Encoding.UTF8.GetBytes (messageToSend);
var sentBytes = await udpClient.SendAsync(data);
return sentBytes == data.Length;
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,SendAsync()在中UdpClient类不接受CancellationToken.
所以我开始将其改为:
public Task<bool> SendAsync(string messageToSend, CancellationToken ct)
{
var udpClient = new UdpClient();
var data = Encoding.UTF8.GetBytes (messageToSend);
var sendTask = udpClient.SendAsync(data);
sendTask.Wait(ct);
if(sendTask.Status == RanToCompletion)
{
return sendTask.Result == data.Length;
}
}
Run Code Online (Sandbox Code Playgroud)
显然这不起作用,因为没有Task被退回.但是,如果我返回任务,则签名不再匹配.SendAsync()返回一个Task<int>,但我需要一个Task<bool>.
而现在我很困惑.:-)如何解决这个问题?
bj0*_*bj0 20
我知道这有点晚了,但我最近不得不取消UdpClient ReceiveAsync/SendAsync.
您的第一个代码块是在没有取消的情况下发送的(您的标题表示顺便收到...).
你的第二个代码块绝对不是这样做的.您正在调用*Async,然后调用Task.Wait,它将一直阻塞,直到调用完成.这使得呼叫有效地同步,并且调用*Async版本没有意义.最好的解决方案是使用Async如下:
...
var sendTask = udpClient.SendAsync(data);
var tcs = new TaskCompletionSource<bool>();
using( ct.Register( s => tcs.TrySetResult(true), null) )
{
if( sendTask != await Task.WhenAny( task, tcs.Task) )
// ct.Cancel() called
else
// sendTask completed first, so .Result will not block
}
...
Run Code Online (Sandbox Code Playgroud)
在UdpClient上没有内置的取消方法(没有一个函数接受a CancellationToken),但你可以利用等待多个任务的能力Task.WhenAny.这将返回第一个完成的任务(这也是一种Task.Delay()用于实现超时的简单方法).然后我们只需要创建一个将在CancellationToken取消时完成的任务,我们可以通过创建一个TaskCompletionSource并使用其CancellationToken回调设置它来完成.
一旦取消,我们可以关闭套接字以实际"取消"底层读/写.
最初的想法是来自处理文件句柄的另一个SO答案,但它也适用于套接字.我通常将它包装在一个扩展方法中,如下所示:
public static class AsyncExtensions
{
public static async Task<T> WithCancellation<T>( this Task<T> task, CancellationToken cancellationToken )
{
var tcs = new TaskCompletionSource<bool>();
using( cancellationToken.Register( s => ( (TaskCompletionSource<bool>)s ).TrySetResult( true ), tcs ) )
{
if( task != await Task.WhenAny( task, tcs.Task ) )
{
throw new OperationCanceledException( cancellationToken );
}
}
return task.Result;
}
}
Run Code Online (Sandbox Code Playgroud)
然后像这样使用它:
try
{
var data = await client.ReceiveAsync().WithCancellation(cts.Token);
await client.SendAsync(data.Buffer, data.Buffer.Length, toep).WithCancellation(cts.Token);
}
catch(OperationCanceledException)
{
client.Close();
}
Run Code Online (Sandbox Code Playgroud)