如何使UdpClient.ReceiveAsync()可取消? [英] How to to make UdpClient.ReceiveAsync() cancelable?

查看:381
本文介绍了如何使UdpClient.ReceiveAsync()可取消?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个方法的接口INetwork:

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

接口的一种实现具有如下代码:

One implementation of the interface has code like this:

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

不幸的是,UdpClient类的SendAsync()不接受CancellationToken.

Unfortunately, SendAsync() of the UdpClient class does not accept a CancellationToken.

所以我开始将其更改为:

So I started changing it to:

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

Obviously this won't work because there is no Task being returned. However if I return the Task, the signatures don't match anymore. SendAsync() returns a Task<int>, but I need a Task<bool>.

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

And now I'm confused. :-) How to resolve this?

推荐答案

我知道这有点晚了,但是最近我不得不使UdpClient ReceiveAsync/SendAsync可取消.

I know this is a little late, but I just recently had to make a UdpClient ReceiveAsync/SendAsync cancellable.

您的第一个代码块正在发送而没有取消(您的标题说顺便说一句...").

Your first code block is sending without a cancel (your title says receive by the way...).

您的第二个代码块绝对不是这样做的方法.您正在调用* Async,然后是Task.Wait,它将阻塞直到调用完成.这使调用有效地同步,并且调用* Async版本毫无意义.最好的解决方案是按如下方式使用Async:

Your second code block is defintely not the way to do it. You are calling *Async, and then Task.Wait, which blocks until the call is complete. This makes the call effectively synchronous and there's no point in calling the *Async version. The best solution is to use Async as follows:

...
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的回调对其进行设置来完成此任务.

There's no built-in way to cancel on UdpClient (none of the functions accept a CancellationToken), but you can take advantage of the ability to await multiple tasks with Task.WhenAny. This will return with the first task that completes (this is also an easy way to use Task.Delay() to implement timeouts). We then just need to create a Task that will complete when the CancellationToken is canceled, which we can do by creating a TaskCompletionSource and setting it with the CancellationToken's callback.

一旦取消,我们就可以关闭套接字以实际上取消"底层的读/写.

Once canceled, we can close the socket to actually "cancel" the underlying read/write.

最初的想法来自另一个SO处理文件句柄的答案,但它也适用于套接字.我通常将其包裹在扩展方法中,如下所示:

The original idea for this came from another SO answer dealing with file handles, but it works with sockets too. I generally wrap it up in an extension method like 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();
}

这篇关于如何使UdpClient.ReceiveAsync()可取消?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆