在 Trio 中,如何在不等待的情况下将数据写入套接字?

In Trio, how do you write data to a socket without waiting?

在 Trio 中,如果您想将一些数据写入 TCP 套接字,那么显而易见的选择是 send_all:

my_stream = await trio.open_tcp_stream("localhost", 1234)
await my_stream.send_all(b"some data")

请注意,这两者都通过套接字发送数据 并且 等待数据被写入。但是如果你只是想把要发送的数据排队,而不是等待它写入​​(至少,你不想在同一个协程中等待)怎么办?

在 asyncio 中这很简单,因为这两部分是独立的函数:write() and drain()。例如:

writer.write(data)
await writer.drain()

当然,如果您只想写入数据而不是等待它,您可以直接调用 write() 而无需等待 drain()。 Trio 中是否有等效的功能?我知道这种双功能设计是有争议的,因为 it makes it hard to properly apply backpressure,但在我的应用程序中我需要将它们分开。

现在我已经通过为每个连接创建一个专用的 writer 协程并有一个内存通道将数据发送到该协程来解决这个问题,但是与在调用一个或两个函数之间进行选择相比,这是相当多的错误,而且看起来有点浪费(大概是引擎盖下还有一个发送缓冲区,而我的内存通道就像那个缓冲区之上的缓冲区)。

我在 Trio 聊天中发布了这个并且 Nathaniel J. Smith, the creator of Trio, replied with this:

Trio doesn't maintain a buffer "under the hood", no. There's just the kernel's send buffer, but the kernel will apply backpressure whether you want it to or not, so that doesn't help you.

Using a background writer task + an unbounded memory channel is basically what asyncio does for you implicitly.

The other option, if you're putting together a message in multiple pieces and then want to send it when you're done would be to append them into a bytearray and then call send_all once at the end, at the same place where you'd call drain in asyncio

(but obviously that only works if you're calling drain after every logical message; if you're just calling write and letting asyncio drain it in the background then that doesn't help)

所以这个问题是基于一个误解:我想写入Trio的隐藏发送缓冲区,但不存在这样的东西!使用一个单独的协程等待流并调用 send_all() 比我想象的更有意义。

我最终使用了两种想法的混合体(使用带有内存通道的单独协程与使用 bytearray):将数据保存到 bytearray,然后使用 条件变量 ParkingLot 向另一个协程发出信号,表明它已准备好写入。这让我可以合并写入,还可以手动检查缓冲区是否变得太大。