15

我有一个INetwork方法接口:

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

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

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()返回 a Task<int>,但我需要 a Task<bool>

现在我很困惑。:-) 如何解决这个问题?

4

3 回答 3

25

我知道这有点晚了,但我最近不得不取消 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 上没有内置的取消方法(没有一个函数接受 a CancellationToken),但您可以利用等待多个任务的能力Task.WhenAny。这将返回完成的第一个任务(这也是一种Task.Delay()用于实现超时的简单方法)。然后我们只需要创建一个任务,它会在CancellationToken取消 时完成,我们可以通过创建 a并使用的回调TaskCompletionSource设置它来完成。CancellationToken

一旦取消,我们可以关闭套接字以实际“取消”底层的读/写。

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

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();
}
于 2014-06-10T18:53:54.520 回答
1

首先,如果你想 return Task<bool>,你可以简单地使用Task.FromResult(). 但是您可能不应该这样做,拥有一个实际上是同步的异步方法没有多大意义。

除此之外,我还认为您不应该假装该方法已取消,即使没有。您可以做的是在启动 real 之前检查令牌SendAsync(),仅此而已。

如果您真的想假装该方法已尽快取消,您可以使用ContinueWith()with cancelation:

var sentBytes = await sendTask.ContinueWith(t => t.Result, ct);
于 2013-10-16T15:19:26.240 回答
0

我建议你给自己发信息。一条消息说:“停止”。

于 2021-12-23T13:06:25.557 回答