2

我已阅读Stephen Toub 的博客,关于为 SocketAsyncEventArgs 制作自定义等待。这一切正常。但我需要的是一个可取消的等待对象,并且该博客不涉及这个主题。不幸的是,Stephen Cleary 也没有在他的书中介绍如何取消不支持取消的异步方法。我试图自己实现它,但我在 TaskCompletionSource 和 Task.WhenAny 上失败了,因为使用 awaitable 我实际上并没有在等待任务。这就是我想要的:能够使用带有 TAP 格式的 Socket 的 ConnectAsync 并能够取消它,同时仍然具有 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;
}
4

1 回答 1

3

SocketAsyncEventArgs 的自定义等待。

这是可行的,但SocketAsyncEventArgs专门针对对性能极为敏感的场景。绝大多数(我的意思是 >99.9%)项目不需要它,可以使用 TAP 代替。TAP 很好,因为它可以与其他技术很好地互操作……比如取消。

我需要的是一个可取消的可等待对象,并且该博客未涵盖此主题。不幸的是,Stephen Cleary 也没有在他的书中介绍如何取消不支持取消的异步方法。

所以,这里有几件事。从“如何取消不支持取消的异步方法”开始:简短的回答是你不能。这是因为取消是合作的;所以如果一方不能合作,那就不可能了。更长的答案是它是可能的,但通常比它的价值更多的麻烦。

在“取消不可取消的方法”的一般情况下,您可以在单独的线程上同步运行该方法,然后中止该线程;这是最有效和最危险的方法,因为中止的线程很容易破坏应用程序状态。为了避免这种严重的损坏问题,最可靠的方法是在单独的进程中运行该方法,该进程可以干净地终止。然而,这通常是矫枉过正,而且比它的价值更多的麻烦。

足够的一般性答案;特别是对于套接字,您不能取消单个操作,因为这会使套接字处于未知状态。以Connect您的问题为例:连接和取消之间存在竞争条件。您的代码在请求取消后可能会以连接的套接字结束,并且您不希望该资源保留。所以取消套接字连接的正常和公认的方法是关闭套接字。

任何其他套接字操作也是如此:如果您需要中止读取或写入,那么当您的代码发出取消时,它无法知道读取/写入完成了多少 - 这将使套接字处于未知状态说明您正在使用的协议。“取消”任何套接字操作的正确方法是关闭套接字

请注意,此“取消”的范围与我们通常看到的取消类型不同。CancellationToken和好友代表取消单次操作;关闭套接字会取消该套接字的所有操作。因此,套接字 API 不接受CancellationToken参数。

不幸的是,Stephen Cleary 在他的书中没有介绍如何取消不支持取消的异步方法。我试图自己实现它

“我如何取消一个不可取消的方法”这个问题几乎从来没有用“使用这个代码块”来解决。具体来说,您使用的方法取消了await,而不是operation。请求取消后,您的应用程序仍在尝试连接到服务器,并且最终可能成功或失败(两个结果都将被忽略)。

在我的 AsyncEx 库中,我确实有几个重载,WaitAsync允许您执行以下操作:

private static async Task MyMethodAsync(CancellationToken cancellationToken)
{
  var connectTask = _socket.ConnectAsync(_host);
  await connectTask.WaitAsync(cancellationToken);
  ...
}

我更喜欢这种 API,因为很明显取消的是异步等待,而不是连接操作。

我使用 TaskCompletionSource 和 Task.WhenAny 失败,因为使用 awaitable 我实际上并没有在等待任务。

好吧,要做这样更复杂的事情,您需要将 awaitable 转换为Task. 可能有一个更有效的选择,但这真的很麻烦。即使你弄清楚了所有的细节,你仍然会得到“假取消”:一个看起来像是取消操作但实际上并没有取消的 API——它只是取消了等待。

这就是我想要的:能够使用带有 TAP 格式的 Socket 的 ConnectAsync 并能够取消它,同时仍然具有 SocketAsyncEventArgs 的可重用性。

TL;DR:您应该关闭套接字而不是取消连接操作。就快速回收资源而言,这是最干净的方法。

于 2019-12-06T15:13:57.640 回答