使用Python asyncio与线程封装实现回调,定时任务,订阅/发布模式

主要使用了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)

  1. 此方法提交一个协程任务到循环中,loop作为参数
  2. 返回Futures供查询结果
  3. 当事件循环运行时, 必须在不同线程下添加协程任务到此循环中*

实现思路:在线程中建立新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
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值