主要使用了asyncio.run_coroutine_threadsafe的方法与单例线程使用
asyncio官方文档
跨线程调度
asyncio.run_coroutine_threadsafe(coro, loop)
向指定事件循环提交一个协程。(线程安全)
返回一个 concurrent.futures.Future 以等待来自其他 OS 线程的结果。
此函数应该从另一个 OS 线程中调用,而非事件循环运行所在线程。示例:
# Create a coroutine
coro = asyncio.sleep(1, result=3)
# Submit the coroutine to a given loop
future = asyncio.run_coroutine_threadsafe(coro, loop)
# Wait for the result with an optional timeout argument
assert future.result(timeout) == 3
如果在协程内产生了异常,将会通知返回的 Future 对象。它也可被用来取消事件循环中的任务:
try:
result = future.result(timeout)
except TimeoutError:
print('The coroutine took too long, cancelling the task...')
future.cancel()
except Exception as exc:
print(f'The coroutine raised an exception: {exc!r}')
else:
print(f'The coroutine returned: {result!r}')
参见 concurrency and multithreading 部分的文档。
不同与其他 asyncio 函数,此函数要求显式地传入 loop 参数。
根据asyncio的文档介绍,asyncio的事件循环不是线程安全的,一个event loop只能在一个线程内调度和执行任务
并且同一时间只有一个任务在运行,当程序调用get_event_loop获取event loop时,会从一个本地的Thread Local对象获取属于当前线程的event loop run_coroutine_threadsafe内部用到了call_soon_threadsafe:
event loop内部会维护着一个self-pipe,它由一对socketpair组成,_write_to_self的作用就是把一个信号写到self-pipe的一端.
这样一来,event loop在检测到self-pipe发生事件后,就会响应并唤醒事件循环来处理任务
asyncio.run_coroutine_threadsafe(coro, loop)
- 此方法提交一个协程任务到循环中,loop作为参数
- 返回Futures供查询结果
- 当事件循环运行时, 必须在不同线程下添加协程任务到此循环中*
实现思路:在线程中建立新event-loop,用asyncio run_coroutine_threadsafe 动态添加协程,线程为单例模式,随时可使用
1.适用做定时器任务
2.订阅与发布模式(没有写取消订阅逻辑)
3.非async函数可以直接调用async函数
4.支持gather模式
5.支持多线程调用
优点:单线程loop事件高复用,适应做任务大于5秒的任务
缺点:不适应做实时性高的任务,任务若太多,会阻塞整个线程运行,任务多建议用gather
一、封装线程代码
# -*- coding: utf-8 -*-
# @Time :2022/6/2 13:14
# @Author : Cre
# @File : AsyncEvent.py
# @Software: PyCharm
import asyncio
import threading
class AsyncEvent ( threading.Thread ):
def __init__( self ):
if not hasattr ( AsyncEvent, "_first_init" ):
print ( "__init__" )
threading.Thread.__init__ ( self )
AsyncEvent._first_init = True
self._loop = asyncio.new_event_loop ()
self.daemon = True
self.callBack = None
self.channels = {}
self.keysIndex = {}
self.start ()
# 单例
def __new__( cls, *args, **kwargs ):
if not hasattr ( AsyncEvent, "_instance" ):
print ( "创建新实例" )
AsyncEvent._instance = object.__new__ ( cls )
return AsyncEvent._instance
@classmethod
def get_instance( cls, *args, **kwargs ):
# hasattr() 函数用于判断对象是否包含对应的属性 , 这里是看看这个类有没有 _instance 属性
if not hasattr ( AsyncEvent, '_instance' ):
return AsyncEvent ( *args, **kwargs )
return AsyncEvent._instance
#线程运行
def run( self ):
asyncio.set_event_loop ( self._loop )
# 在新线程中开启一个事件循环
self._loop.run_forever ()
def subscribe( self, func, channel ):
'''
订阅事件
:param func: 回调方法
:param channel: 订阅事件 order XXX
:return:
'''
if channel not in self.channels:
self.channels[channel] = []
self.channels[channel].append ( func )
async def __callBack( self, channel, msg ):
if channel in self.channels:
for x in self.channels[channel]: await x ( msg )
def pubilc( self, channel: str, msg ):
'''
发布事件
:param channel: 事件
:param msg: 内容
:return:
'''
if channel is not None:
asyncio.run_coroutine_threadsafe ( self.__callBack ( channel, msg ), self._loop )
def runFun( self, func ):
asyncio.run_coroutine_threadsafe ( func (), self._loop )
async def __runGather( self, funclist ):
task_list = []
for f in funclist:
task = asyncio.create_task ( f )
task_list.append ( task )
await asyncio.gather ( *task_list )
def runGather( self, funclist ):
asyncio.run_coroutine_threadsafe ( self.__runGather ( funclist ), self._loop )
def getLoop( self ):
return self._loop
def close( self ):
if self._loop is not None:
self._loop.close ()
AsyncEvent._instance = None
delattr(AsyncEvent,'_instance')
测试
# -*- coding: utf-8 -*-
# @Time :2022/6/2 13:47
# @Author : Cre
# @File : testEv.py
# @Software: PyCharm
import asyncio
import threading
import time
from AsyncEvent import AsyncEvent
try:
import thread
except ImportError:
import _thread as thread
# 子线程1调用
def run1():
i = 0
while True:
i += 1
if i % 2 == 0:
msg = {"name": "我是订单数据", "i": i / 2}
AsyncEvent.get_instance ().pubilc ( "order", msg )
time.sleep ( 2 )
# print ( "子线程1", i, threading.get_ident () )
# 子线程2调用
def run2():
i = 0
while True:
i += 1
if i % 3 == 0:
msg = {"name": "我是登录数据", "i": i / 3}
AsyncEvent.get_instance ().pubilc ( "login", msg )
time.sleep ( 2 )
# print ( "子线程2", i, threading.get_ident () )
# 死循环只适用做大于5秒的定时任务,不然会占用整个线程
async def Dosothing():
i = 0
while True:
# 1.死循环中,建设不能做执行时间太久的活,不然会影响整个线程
# 2.要执行太长的活,使用async/await综合使用
i += 1
print ( "Dosothing 死循环 one", i )
await asyncio.sleep ( 5 ) # 一定要加等待,让线程去干其他协程的活
async def Dosothing1():
i = 0
while True:
# 1.死循环中,建设不能做执行时间太久的活,不然会影响整个线程
# 2.要执行太长的活,使用async/await综合使用
print ( "Dosothing 死循环 two", i )
i += 1
await asyncio.sleep ( 5 ) # 一定要加等待,让线程去干其他协程的活
async def callOrder( msg ):
print ( "callOrder", msg, threading.get_ident () )
# await asyncio.sleep ( 1 )
async def callOrder1( msg ):
print ( "callOrder1--->", msg, threading.currentThread ().name )
# await asyncio.sleep ( 1 )
async def callLogin( msg ):
print ( "callLogin", msg, threading.get_ident () )
# await asyncio.sleep ( 1 )
def callPay( msg ):
print ( "callPay", msg )
def callChange( msg ):
print ( "callChange", msg )
async def taskPrint( msg ):
print ( "task并发", msg, threading.currentThread ().name, time.time () )
async def taskPrint2( msg ):
print ( "task并发2", msg, threading.currentThread ().name, time.time () )
if __name__ == '__main__':
a = AsyncEvent ()
# 订阅/发布模式 async 方式 ,可以调用其他协程
a.subscribe ( callOrder, "order" )
#群聊
a.subscribe ( callOrder1, "order" )
a.subscribe ( callOrder1, "order" )
a.subscribe ( callOrder1, "order" )
a.subscribe ( callLogin, "login" )
# 订阅/发布模式 普通方式
a.subscribe ( callPay, "pay" )
a.subscribe ( callChange, "change" )
# 直接运行
a.runFun ( Dosothing1 )
a.runFun ( Dosothing )
thread.start_new_thread ( run1, () )
thread.start_new_thread ( run2, () )
i = 0
while (True):
i += 1
if i % 6 == 0:
a.pubilc ( "pay", f"我是支付数据 :{i/6}" )
elif i % 8 == 0:
a.pubilc ( "change", f"我是修改 :{i/8}" )
elif i % 10 == 0:
# 用task方式,方法一定要为async
tasks = []
vid = i / 10
for v in range ( 20 ):
msg = {"name": "我来测试task", 'id': vid, 'val': v}
tasks.append ( taskPrint ( f"我来测试task{vid} value={v} " ) )
tasks.append ( taskPrint2 ( msg ) )
a.runGather ( tasks )
# line = input ( "输入q退出:" )
# if (line == 'q'):
# break
time.sleep ( 1 )
运行结果
创建新实例
__init__
Dosothing 死循环 two 0
Dosothing 死循环 one 1
callOrder {'name': '我是订单数据', 'i': 1.0} 8140
callOrder1---> {'name': '我是订单数据', 'i': 1.0} Thread-1
callOrder1---> {'name': '我是订单数据', 'i': 1.0} Thread-1
callOrder1---> {'name': '我是订单数据', 'i': 1.0} Thread-1
callLogin {'name': '我是登录数据', 'i': 1.0} 8140
Dosothing 死循环 two 1
Dosothing 死循环 one 2
callPay 我是支付数据 :1.0
callOrder {'name': '我是订单数据', 'i': 2.0} 8140
callOrder1---> {'name': '我是订单数据', 'i': 2.0} Thread-1
callOrder1---> {'name': '我是订单数据', 'i': 2.0} Thread-1
callOrder1---> {'name': '我是订单数据', 'i': 2.0} Thread-1
callChange 我是修改 :1.0
task并发 我来测试task1.0 value=0 Thread-1 1654176169.6121645
task并发2 {'name': '我来测试task', 'id': 1.0, 'val': 0} Thread-1 1654176169.6121645
task并发 我来测试task1.0 value=1 Thread-1 1654176169.6121645
task并发2 {'name': '我来测试task', 'id': 1.0, 'val': 1} Thread-1 1654176169.6121645
task并发 我来测试task1.0 value=2 Thread-1 1654176169.6121645
task并发2 {'name': '我来测试task', 'id': 1.0, 'val': 2} Thread-1 1654176169.6121645
task并发 我来测试task1.0 value=3 Thread-1 1654176169.6121645
task并发2 {'name': '我来测试task', 'id': 1.0, 'val': 3} Thread-1 1654176169.6121645
task并发 我来测试task1.0 value=4 Thread-1 1654176169.6121645
task并发2 {'name': '我来测试task', 'id': 1.0, 'val': 4} Thread-1 1654176169.6121645
task并发 我来测试task1.0 value=5 Thread-1 1654176169.6121645
task并发2 {'name': '我来测试task', 'id': 1.0, 'val': 5} Thread-1 1654176169.6121645
task并发 我来测试task1.0 value=6 Thread-1 1654176169.6121645