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

        最近在写一个开源项目的时候因为功能较多所以分了几个功能模块,期望是在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文件中的部分代码:

from core import statusupdate, bilidynamic

if __name__ == '__main__':
    threads = []    # 子线程列表
    lock = threading.Lock()  # 线程锁
    loop = asyncio.new_event_loop()  # 创建事件循环传入模块
    asyncio.set_event_loop(loop)

    
    statusupdate = threading.Thread(target=statusupdate.run, name='statusupdate', args=(loop, lock))
    threads.append(statusupdate)

    
    bilidynamic = threading.Thread(target=bilidynamic.run, name='bilidynamic', args=(loop, lock))
    threads.append(bilidynamic)

    for thread in threads:
        thread.start()
    for thread in threads:
        thread.join()

        至此,这个让我花费了20个小时的bug才得以解决,我并不清楚底层的逻辑原理,但我猜测是多线程中只有一个事件循环,而第一个运行的子线程会自动占用唯一的事件循环从而使其他子线程无法在这个事件循环中注册task,当然这只是结果论的猜测,如果您能告诉我底层逻辑我将不胜感激。

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Python,`threading`模块提供了多线程编程的功能。要重启一个线程,可以按照以下步骤进行操作: 1. 首先,确保线程已经结束或者已经停止。你可以使用`threading.Thread.is_alive()`方法检查线程是否在运行。 2. 如果线程已经停止或者结束,你需要创建一个新的线程对象来代替原来的线程。 3. 创建新的线程对象后,可以调用`start()`方法来启动线程。这将会调用线程对象的`run()`方法,开始执行线程任务。 下面是一个简单的示例代码,演示了如何重启一个线程: ```python import threading import time class MyThread(threading.Thread): def __init__(self): super().__init__() def run(self): # 线程任务 print("Thread started") time.sleep(2) print("Thread finished") # 创建并启动线程 thread = MyThread() thread.start() # 等待线程结束 thread.join() # 检查线程是否结束 if not thread.is_alive(): # 创建新的线程对象并启动 new_thread = MyThread() new_thread.start() ``` 在这个示例,我们首先创建一个自定义的`MyThread`类,继承自`threading.Thread`类,并实现了`run()`方法作为线程任务。 然后,我们创建并启动了一个线程对象`thread`,等待它执行完毕。 之后,我们检查线程是否结束,如果已经结束,就创建一个新的线程对象`new_thread`,并调用`start()`方法来启动它。 这样就完成了线程的重启操作。注意,这里的重启是通过创建一个新的线程对象来实现的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值