import asyncio


def create_interval(coro, ms):
	async def inner():
		i = 0
		while True:
			print(f'{i} round')
			await asyncio.sleep(ms/1000)
			await coro()
			i += 1
	return asyncio.create_task(inner())


def task_error_handler(cb):
	def decorator(func):
		async def wrapper(*args, **kwargs):
			try:
				result = await func(*args, **kwargs)
			except Exception as exc:
				result = await cb(exc)
			return result
		return wrapper
	return decorator
	

async def simple_err_handler(exc):
	result = {'error': str(type(exc))}
	print(f'EXC OCCURED: {exc}')
	await asyncio.sleep(0.5)
	return result
	

async def test1():
	print('START TEST1')
	
	@task_error_handler(simple_err_handler)
	async def inner(name):
		print(f'{name} STARTED')
		await asyncio.sleep(0.5)
		raise RuntimeError('u suck')
		print('NEVER SHOULD BE PRINTED')
		await asyncio.sleep(0.5)
		
	t1 = asyncio.create_task(inner('Beta'))
	await asyncio.sleep(2)
	t1.cancel()
	print('TASK CANCELLED')
	await asyncio.sleep(0.5)
	print('END TEST1')
	
	
async def test2():
	print('START TEST2')
	
	@task_error_handler(simple_err_handler)
	async def inner(name):
		print(f'{name} STARTED')
		await asyncio.sleep(0.5)
		raise RuntimeError('u suck')
		print('NEVER SHOULD BE PRINTED')
		await asyncio.sleep(0.5)
		
	t1 = create_interval(inner('Charly'), 500)
	await asyncio.sleep(2)
	t1.cancel()
	print('TASK CANCELLED')
	await asyncio.sleep(0.5)
	print('END TEST2')
	
	
def main():
	asyncio.run(test1())  # не упадет
	asyncio.run(test2())  # тихо сдыхает
	

if __name__ == '__main__':
	main()