最近我发现了一个提供同步和异步接口的库。可以通过 async
特性标志启用异步,而异步/同步函数则通过编译器指令进行区分。
例如,这是一个同步函数的样子:
#[cfg(not(feature = "async"))]
fn perform_query<A: ToSocketAddrs>(&self, payload: &[u8], addr: A) -> Result<Vec<u8>>
{
// More than 100 lines of code with occasional calls to sync UdpSocket::send_to and recv.
}
这就是一个异步函数的样子:
#[cfg(feature = "async")]
async fn perform_query<A: ToSocketAddrs>(&self, payload: &[u8], addr: A) -> Result<Vec<u8>>
{
// More than 100 lines of code with occasional calls to async UdpSocket::send_to and recv.
// Apart from 3-4 await lines, it does mostly the same thing as its sync counterpart.
}
我在同步代码中发现并修复了一些错误,现在我准备将修复方案实施到异步代码中。但是我注意到由于这个大函数完全重复,我需要将我的修复补丁应用到异步函数中,然后我开始思考,为什么这个函数的大部分内容首先被复制呢?长期来看,维护这段代码似乎很困难,所以我想通过去重这个函数来帮忙... 然后我遇到了问题,让我意识到这并不像我想象的那么简单。我可以使用编译器指令区分这些行,并且我甚至可以编写一个宏,根据是否启用“async”功能来插入“UdpSocket”调用的同步/异步版本。但是我意识到我无法通过编译器指令选择函数头,因为“#[cfg...]”将应用于整个函数,所以如果我做这样的事情,我会得到大量的语法错误:
#[cfg(not(feature = "async"))]
fn perform_query<A: ToSocketAddrs>(&self, payload: &[u8], addr: A) -> Result<Vec<u8>>
#[cfg(feature = "async")]
async fn perform_query<A: ToSocketAddrs>(&self, payload: &[u8], addr: A) -> Result<Vec<u8>>
{
// Deduplicated code with occasional differentiation of sync / async UdpSocket calls.
}
我也考虑过只使用核心的异步函数,然后使用异步和同步包装器函数来调用它,无论库是编译为同步还是异步,但是我不能从同步函数中调用异步函数,或者至少需要使用异步运行时进行一些丑陋的魔法来await
/poll
函数,然后将结果作为同步传递,但是库的同步构建也必须导入异步运行时,最好避免这种情况。
我的当前想法是将数据包的处理移动到单独的同步函数中,这些函数将从同步和异步包装器中调用,这些函数仅处理实际的UdpSocket
调用,但我不确定这是否是正确的方法。我的意思是,难道没有更平滑、更优雅的方法吗?对于这个问题,一般的方法是什么?或者复制庞大的函数以供同步和异步构建使用是正常的吗?正如你所猜测的那样,我没有异步编程的经验。
perform_query
和perform_query_async
),通常应具有单独命名的函数。 - kmdrekomylib::sync::x
和mylib::async::x
。这样你就可以导入你想要的那个,避免一直添加_async
的麻烦。 - tadmanmaybe_async
crate感兴趣。 - cdhowie