c# - 如何使 UdpClient.ReceiveAsync() 可取消?

标签 c# asynchronous async-await

我有一个接口(interface) INetwork用一个方法:

Task<bool> SendAsync(string messageToSend, CancellationToken ct)

接口(interface)的一个实现有这样的代码:

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

不幸的是,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;
  }
}

显然这行不通,因为没有 Task被退回。但是,如果我返回任务,签名将不再匹配。 SendAsync()返回 Task<int> , 但我需要一个 Task<bool> .

现在我很困惑。 :-) 如何解决?

最佳答案

我知道这有点晚了,但我最近不得不让 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
}
...

在 UdpClient 上没有内置的取消方法(没有一个函数接受 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;
    }
}

然后像这样使用它:

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();
}

关于c# - 如何使 UdpClient.ReceiveAsync() 可取消?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19404199/

相关文章:

c# - VS中对象关系设计器和实体数据模型设计器的区别

java - 在 Java 中并行执行代码

c# - 如何将一个 IEnumerable 异步转换为另一个 IEnumerable,就像 LINQ 的 Select() 一样,但对每个转换后的项使用 wait ?

c# - 对 MVC 中的实体所做的更改未提交到数据库

c# - 如何使用 AutoMapper 从多个对象映射单个对象?

c# - 立即从 DataTable 中删除行,而无需循环,其中列具有 Null 值

javascript - 如何从异步调用返回响应?

sql - 如何等待异步完成 - NodeJs

c# - 调用 await GetFileAsync() 永远不会返回并且应用程序在 WinRT 应用程序中挂起

javascript - 尝试使用 async/await 而不是 browser.sleep,但它不起作用