Python中使用threading多线程调用含asyncio异步函数的自定义模块中出现的EventLoop冲突

Python中使用threading多线程调用含asyncio异步函数的自定义模块中出现的EventLoop冲突
LeK本文首发于CSDN,现迁移到本博客。
最近在写一个开源项目的时候因为功能较多所以分了几个功能模块,期望是在main主程序中以多线程的方式调用这些模块,因为涉及网络IO且调用的API中的函数为异步函数,因此功能模块使用了异步编程即使用asyncio。
在测试功能模块时,当这些模块以主程序的方式运行时可以正常工作,但一旦将这些模块导入到main文件中进行多线程调用就会出现有的模块正常运行而有的模块产生”Timeout context manager should be used inside a task”错误,说明模块中的异步函数并没有作为task注册到EventLoop中。
我尝试使用asyncio.create_task()手动将协程注册到EventLoop,但是依然无法成功注册,于是开始疯狂debug,先后尝试了在模块中set一个新EventLoop、使用loop.create_task()、使用各种方法想建立一个EventLoop和创建task,从发现这个bug开始27小时里我花了20个小时进行各种测试、查资料、换方法,但是始终不能使这个task成功在EventLoop上注册。
如同无头苍蝇般的我开始重新找asyncio的教程、开始用各种分析工具、甚至开始看aiohttp源码和调用的api库的源码……
在注册task无果后我开始思考既然这个模块单独运行是正常的,那么有没有可能是多线程threading的原因呢?而且我发现一个非常吊诡的现象是,第一个start的线程中的模块是能够正常运行的。
于是我将第一个线程屏蔽,单独剩下出现error的线程,神奇的事情发生了,这个模块能够正常工作了!这说明问题一定出在多线程上,但是为什么使用了多线程会导致task无法注册且就算是我在子线程中单独set EventLoop也无法成功呢?
我认为是第一个start的线程占用了EventLoop,于是我又花了大量精力希望能够在每个子线程中创建EventLoop以时协程正常注册,除了之前尝试的方法之外我开始根据别人写的关于多线程与协程的代码使用asyncio.run_coroutine_threadsafe(),无果。看到这你会发现,这不是和我上面的工作一样吗?^^所以我当时都要改吐了……
如果你也遇到类似的问题并且解决不了的话,也许我接下来的方法能帮助到你。
虽然不太懂原理,最后我尝试在main文件中建立一个loop并将这个loop传入模块中使用,这个方法令模块得以成功运行,下面是我的main文件中的部分代码:
1 | from core import statusupdate, bilidynamic |
至此,这个让我花费了20个小时的bug才得以解决,我并不清楚底层的逻辑原理,但我猜测是多线程中只有一个事件循环,而第一个运行的子线程会自动占用唯一的事件循环从而使其他子线程无法在这个事件循环中注册task,当然这只是结果论的猜测,如果您能告诉我底层逻辑我将不胜感激。