我正在制作一个SocketExtender
课程,该课程将为async/await Task
课程提供Socket
个扩展方法。在我的扩展方法中,我添加的是能够通过Socket
参数取消ConnectAsync
操作,例如ReceiveAsync
,SendAsync
或CancellationToken
。< / p>
在最佳方式上做一些阅读,我决定将调用包裹在TaskCompletionSource<T>
中并传回Task
进行操作。
例如,我想将BeginReceive
和EndReceive
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
吗?或者这会像我希望的那样工作吗?
答案 0 :(得分:3)
tcs.SetCanceled(); //&lt; - 是的,这是.NET框架中的拼写错误! :)
实际上,美国版的英语版本很奇怪。英国(和其他)英语将使用Cancelled
,但美国英语使用Canceled
。请注意,Cancellation
在每个英语版本中始终使用两个l
。
所以我需要CancellationToken来触发一个Socket.Close()调用,通过token.Register()
我不同意。取消的约定是仅取消该操作。因此,如果你有一个取消令牌的Send
操作,我会惊讶地发现发送取消的语义也会导致任何Receive
操作失败,并进一步使整个套接字无法使用。
在异步调用完成之前,是否会通过使用处理CancellationTokenRegistration?
是。对编译器的部分没有神奇的理解。
我是否需要关心在这里处理CancellationTokenRegistration?我应该保留对它的引用并在finally块中调用Dispose()吗?或者这会像我希望的那样工作吗?
我建议不要在各个操作调用中使用CancellationToken
。它在逻辑上是一个对象级取消,因此创建一个单独的类并将其传递给构造函数是一个更合适的设计IMO。但是,如果你坚持认为,finally
块听起来是一个好方法。