CancellationTokenRegistration.Dispose in Async Task

时间:2013-11-30 00:54:44

标签: c# asynchronous task dispose cancellation

我正在制作一个SocketExtender课程,该课程将为async/await Task课程提供Socket个扩展方法。在我的扩展方法中,我添加的是能够通过Socket参数取消ConnectAsync操作,例如ReceiveAsyncSendAsyncCancellationToken。< / p>

在最佳方式上做一些阅读,我决定将调用包裹在TaskCompletionSource<T>中并传回Task进行操作。

例如,我想将BeginReceiveEndReceive APM方法包装到以下基于Task的方法中:

public static Task<int> ReceiveAsync(this Socket socket, byte[] buffer, int offset, int count, SocketFlags flags, CancellationToken token) 
{
  var tcs = new TaskCompletionSource<int>();

  socket.BeginReceive(buffer, offset, count, flags, result => 
  {
    try
    {
      var bytesReceived = socket.EndReceive(result);
      tcs.SetResult(bytesReceived);
    }
    catch(Exception ex)
    {
      if(token.IsCancellationRequested)
        tcs.SetCanceled();  // <- Yes, this is a typo in the .NET framework! :)
      else
        tcs.SetException(ex);
    }

  });

  return tcs.Task;
}

注意:上述方法实际实现取消!是的,它会处理它,但它不会导致Socket实际取消操作。所以我需要CancellationToken通过Socket.Close()触发token.Register()来电。没什么大不了的,但问题变成如何我是否正确处理了返回的CancellationTokenRegistration对象?

这是我的第一个想法:

public static Task<int> ReceiveAsync(this Socket socket, byte[] buffer, int offset, int count, SocketFlags flags, CancellationToken token) 
{
  var tcs = new TaskCompletionSource<int>();

  using(token.Register(socket.Close))  // <- Here we are ensuring disposal of the CTR
  {
    socket.BeginReceive(buffer, offset, count, flags, result => 
    {
      try
      {
        var bytesReceived = socket.EndReceive(result);
        tcs.SetResult(bytesReceived);
      }
      catch(Exception ex)
      {
        if(token.IsCancellationRequested)
          tcs.SetCanceled();  // <- Yes, this is a typo in the .NET framework! :)
        else
          tcs.SetException(ex);
      }
    });
  }

  return tcs.Task;
}

但是,我的问题是,在异步调用完成之前CancellationTokenRegistration是否会通过using处理掉?我的第一直觉想说,因为socket.BeginReceive调用不会阻塞并立即返回,导致using语句在方法返回tcs.Task后清除。

但话说回来,也许C#编译器'理解'我正在做什么,并会看到在子范围内执行一个匿名方法,并且会发生一些黑魔法让它按照我想要的方式工作。

我甚至需要关心在这里处理CancellationTokenRegistration吗?我应该保留对它的引用并在Dispose()块中调用finally吗?或者这会像我希望的那样工作吗?

1 个答案:

答案 0 :(得分:3)

  

tcs.SetCanceled(); //&lt; - 是的,这是.NET框架中的拼写错误! :)

实际上,美国版的英语版本很奇怪。英国(和其他)英语将使用Cancelled,但美国英语使用Canceled。请注意,Cancellation在每个英语版本中始终使用两个l

  

所以我需要CancellationToken来触发一个Socket.Close()调用,通过token.Register()

我不同意。取消的约定是仅取消该操作。因此,如果你有一个取消令牌的Send操作,我会惊讶地发现发送取消的语义也会导致任何Receive操作失败,并进一步使整个套接字无法使用。

  

在异步调用完成之前,是否会通过使用处理CancellationTokenRegistration?

是。对编译器的部分没有神奇的理解。

  

我是否需要关心在这里处理CancellationTokenRegistration?我应该保留对它的引用并在finally块中调用Dispose()吗?或者这会像我希望的那样工作吗?

我建议不要在各个操作调用中使用CancellationToken。它在逻辑上是一个对象级取消,因此创建一个单独的类并将其传递给构造函数是一个更合适的设计IMO。但是,如果你坚持认为,finally块听起来是一个好方法。