我读过Stephen Toub关于为SocketAsyncEventArgs创建自定义awaitable的博客。这一切都很好。但我需要的是一个可取消的awaitable和博客不涵盖这个主题。不幸的是,Stephen Cleary在他的书中没有介绍如何取消不支持取消的异步方法。我试着自己实现它,但我在TaskCompletionSource和Task.WhenAny上失败了,因为有了awaitable,我实际上并没有在等待任务。这就是我想要的:能够使用Socket的ConnectAsync和TAP格式,并能够取消它,同时仍然具有SocketAsyncEventArgs的可重用性。
public async Task ConnectAsync(SocketAsyncEventArgs args, CancellationToken ct) {}
到目前为止,这就是我从Stephen Toub的博客中得到的内容(以及SocketAwaitable实现):
public static SocketAwaitable ConnectAsync(this Socket socket,
SocketAwaitable awaitable)
{
awaitable.Reset();
if (!socket.ConnectAsync(awaitable.m_eventArgs))
awaitable.m_wasCompleted = true;
return awaitable;
}
我只是不知道如何将其转换为TAP格式并使其可取消。感谢您的帮助。
编辑1:这是我通常如何取消的示例:
private static async Task<bool> ConnectAsync(SocketAsyncEventArgs args, CancellationToken cancellationToken)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
cancellationToken.Register(() =>
{TaskCompletionSource.Task
taskCompletionSource.TrySetCanceled();
});
// This extension method of Socket not implemented yet
var task = _socket.ConnectAsync(args);
var completedTask = await Task.WhenAny(task, taskCompletionSource.Task);
return await completedTask;
}
可用于SocketAsyncEventArgs的自定义。
这是可行的,但SocketAsyncEventArgs
专门用于对性能极为敏感的场景。绝大多数(我的意思是>99.9%)的项目不需要它,可以使用TAP。TAP很好,因为它可以与其他技术很好地互操作。。。比如取消。
我需要的是一个可取消的awaitable,博客不涉及这个主题。不幸的是,Stephen Cleary在他的书中没有介绍如何取消不支持取消的异步方法。
所以,这里有几件事。从"如何取消不支持取消的异步方法"开始:简单的答案是你不能。这是因为取消是合作的;因此,如果一方不能合作,那是不可能的。更长远的答案是,这是可能的,但通常会带来更多的麻烦。
在"取消不可取消的方法"的一般情况下,可以在单独的线程上同步运行该方法,然后中止该线程;这是最有效和最危险的方法,因为中止的线程很容易破坏应用程序状态。为了避免这种严重的损坏问题,最可靠的方法是在单独的进程中运行该方法,该进程可以干净地终止。然而,这通常是矫枉过正的,而且麻烦远大于它的价值。
一般的答案已经够多了;特别是对于套接字,不能取消单个操作,因为这会使套接字处于未知状态。以问题中的Connect
为例:连接和取消之间存在竞争条件。您的代码在请求取消后可能会出现一个连接的套接字,并且您不希望该资源一直存在。因此,取消套接字连接的正常且可接受的方法是关闭套接字。
其他套接字操作也是如此:如果你需要中止读取或写入,那么当你的代码发出取消时,它就不知道完成了多少读取/写入,这会使套接字处于与你使用的协议有关的未知状态。"取消"任何套接字操作的正确方法是关闭套接字。
请注意,这种"取消"的范围与我们通常看到的取消不同。CCD_ 3和好友表示取消单个操作;关闭套接字将取消该套接字的所有操作。因此,套接字API不接受CancellationToken
参数。
Stephen Cleary在他的书中没有提到如何取消不支持取消的异步方法。我试着自己实现
"如何取消不可取消的方法"的问题几乎从未用"使用此代码块"正确解决过。具体来说,您使用的方法取消了等待,而不是操作。请求取消后,您的应用程序仍在尝试连接到服务器,最终可能会成功或失败(这两个结果都将被忽略)。
在我的AsyncEx库中,我确实有几个WaitAsync
重载,它们允许您执行以下操作:
private static async Task MyMethodAsync(CancellationToken cancellationToken)
{
var connectTask = _socket.ConnectAsync(_host);
await connectTask.WaitAsync(cancellationToken);
...
}
我更喜欢这种API,因为很明显,取消的是异步等待,而不是连接操作。
我的TaskCompletionSource和Task.WhenAny失败了,因为我实际上并没有在等待任务。
好吧,要做这样更复杂的事情,您需要将该awaitable转换为Task
。也许还有一个更有效的选择,但这真的很难。即使你弄清楚了所有的细节,你仍然会得到"假取消":一个API,看起来像是取消了操作,但实际上并没有——它只是取消了等待。
这就是我想要的:能够使用Socket的ConnectAsync和TAP格式,并能够取消它,同时仍然具有SocketAsyncEventArgs的可重用性。
TL;DR:您应该关闭套接字,而不是取消连接操作。就快速回收资源而言,这是最干净的方法。