Coroutines_协程

'''
协程
协程是实现并发编程的一种方式, 协程通过 async/await 语法进行声明,是编写异步应用的推荐方式
async 修饰词声明异步函数,于是,这里的 crawl_page 和 main 都变成了异步函数。
    print(crawl_page('')): 调用异步函数,我们便可得到一个协程对象(coroutine object)
await 调用可等待对象, 可等待对象有三种主要类型: 协程, 任务 和 Future.
    await可以从任务中切入也可以切出

协程的执行:
1. asyncio.run(main()),程序进入 main() 函数,事件循环开启. Python 3.7 之后才有的特性,
    可以让 Python 的协程接口变得非常简单,你不用去理会事件循环怎么定义和怎么使用的问题
2. await: await 执行的效果,和 Python 正常执行是一样的,程序会阻塞在这里,进入被调用的协程函数,执行完毕返回后再继续
3. asyncio.create_task() 函数: 并发运行作为 asyncio 任务 的多个协程. 注册就开始执行
4. asyncio.gather(*[]): 并发 运行 aws 序列中的 可等待对象。
    如果 aws 中的某个可等待对象为协程,它将自动作为一个任务加入日程。
    如果所有可等待对象都成功完成,结果将是一个由所有返回值聚合而成的列表。结果值的顺序与 aws 中可等待对象的顺序一致。
    如果 return_exceptions 为 False (默认),所引发的首个异常会立即传播给等待 gather() 的任务。
        aws 序列中的其他可等待对象 不会被取消 并将继续运行。
    如果 return_exceptions 为 True,异常会和成功的结果一样处理,并聚合至结果列表。
    如果 gather() 被取消,所有被提交 (尚未完成) 的可等待对象也会 被取消。
    如果 aws 序列中的任一 Task 或 Future 对象 被取消,它将被当作引发了 CancelledError 一样处理 -- 
    在此情况下 gather() 调用 不会 被取消。这是为了防止一个已提交的 Task/Future 被取消导致其他 Tasks/Future 也被取消

Note
1. 协程和多线程的区别,主要在于两点,一是协程为单线程;二是协程由用户决定,在哪些地方交出控制权,切换到下一个任务。
2. 协程的写法更加简洁清晰,把 async / await 语法和 create_task 结合来用,对于中小级别的并发需求已经毫无压力。
3. 写协程程序的时候,你的脑海中要有清晰的事件循环概念,知道程序在什么时候需要暂停、等待 I/O,什么时候需要一并执行到底
'''
import asyncio
import aiohttp

from bs4 import BeautifulSoup
import time
import random


async def crawl_page(url):
    print(url, f"started at {time.strftime('%X')}")
    await asyncio.sleep(int(url.split('_')[-1]))
    print(url, f"end at {time.strftime('%X')}")


async def main(urls):
    # await
    # for url in urls:
    #     await crawl_page(url)
    tasks = [asyncio.create_task(crawl_page(url)) for url in urls]
    # for task in tasks:
    #     await task
    # *tasks 解包列表,将列表变成了函数的参数;** dict 将字典变成了函数的参数
    await asyncio.gather(*tasks)
print(f"started at {time.strftime('%X')}")
asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))
print(f"end at {time.strftime('%X')}")

# 给某些协程任务限定运行时间,一旦超时就取消


async def w1():
    await asyncio.sleep(1)
    print('w1')
    return 1


async def w2():
    await asyncio.sleep(4)
    print('w2')
    return 2/0


async def w3():
    await asyncio.sleep(3)
    print('w3')
    return 3


async def main3():
    t1 = asyncio.create_task(w1())
    t2 = asyncio.create_task(w2())
    t3 = asyncio.create_task(w3())
    await asyncio.sleep(2)
    # 先打印了w1 和 w2, 然后打印sleep end
    print(f"main3 sleep end at {time.strftime('%X')}")
    t3.cancel()
    res = await asyncio.gather(t1, t2, t3, return_exceptions=True)
    print(res)
print(f"main3 started at {time.strftime('%X')}")
asyncio.run(main3())
print(f"main3 end at {time.strftime('%X')}")


# 生产者消费者模型
async def consumer(queue, id):
    while True:
        val = await queue.get()
        print('{} get a val: {}'.format(id, val))
        await asyncio.sleep(1)


async def producer(queue, id):
    for _ in range(5):
        val = random.randint(1, 10)
        await queue.put(val)
        print('{} get a val: {}'.format(id, val))
        await asyncio.sleep(1)


async def maincp():
    queue = asyncio.Queue()
    cs1 = asyncio.create_task(consumer(queue, 'consumer1'))
    cs2 = asyncio.create_task(consumer(queue, 'consumer2'))

    pd1 = asyncio.create_task(producer(queue, 'producer1'))
    pd2 = asyncio.create_task(producer(queue, 'producer2'))

    await asyncio.sleep(10)
    cs1.cancel()
    cs2.cancel()

    await asyncio.gather(cs1, cs2, pd1, pd2, return_exceptions=True)
print(f"maincp started at {time.strftime('%X')}")
asyncio.run(maincp())
print(f"maincp end at {time.strftime('%X')}")

# Movie Spider


async def fecth_content(url):
    header = {
        "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) \
            Chrome/75.0.3770.100 Safari/537.36"}
    async with aiohttp.ClientSession(
            headers=header, connector=aiohttp.TCPConnector(verify_ssl=False, ssl=False)) as session:
        async with session.get(url) as resp:
            return await resp.text()


async def spider():
    url = "https://movie.douban.com/cinema/later/beijing/"
    init_page = await fecth_content(url)
    init_soup = BeautifulSoup(init_page, 'lxml')

    movie_names, urls_fetch, movie_dates = [], [], []

    all_movies = init_soup.find('div', id='showing-soon')
    for movie in all_movies.find_all('div', class_='item'):
        a_tag = movie.find_all('a')
        li_tag = movie.find_all('li')

        movie_names.append(a_tag[1].text)
        urls_fetch.append(a_tag[1]['href'])
        movie_dates.append(li_tag[0].text)

    tasks = [fecth_content(url) for url in urls_fetch]
    pages = await asyncio.gather(*tasks)

    for name, date, page in zip(movie_names, movie_dates, pages):
        soup_item = BeautifulSoup(page, 'lxml')
        img_tag = soup_item.find('img')
        print('{} {} {}'.format(name, date, img_tag['src']))
print(f"movie spider started at {time.strftime('%X')}")
asyncio.run(spider())
print(f"movie spider end at {time.strftime('%X')}")

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
协程中,可以使用协程作用域中的 `cancel()` 函数来取消一个协程。当协程被取消时,它会抛出一个 `CancellationException` 异常。如果在协程中捕获了这个异常,那么协程的执行会立即结束。 然而,在某些情况下,我们可能不想让协程被取消,或者不想让它在取消时抛出异常。这时候我们就可以使用 `SupervisorJob` 和 `SupervisorScope` 来管理协程的取消操作。 `SupervisorJob` 是一个特殊的 `Job` 类型,它会将所有子协程放入一个单独的协程作用域中,从而使它们互相独立。当一个子协程被取消时,其它子协程不会受到影响。同时,如果使用 `SupervisorScope` 来启动子协程,则在取消时也不会抛出 `CancellationException` 异常。 下面是一个例子,展示了如何在协程中使用 `SupervisorJob` 和 `SupervisorScope`: ```kotlin import kotlinx.coroutines.* fun main() = runBlocking { val supervisor = SupervisorJob() val scope = CoroutineScope(coroutineContext + supervisor) val job1 = scope.launch { println("Child coroutine 1 started") delay(1000) println("Child coroutine 1 completed") } val job2 = scope.launch { println("Child coroutine 2 started") try { delay(3000) println("Child coroutine 2 completed") } catch (e: CancellationException) { // do nothing } } delay(2000) job1.cancel() job2.cancel() supervisor.children.forEach { it.join() } println("All coroutines completed") } ``` 在这个例子中,我们首先创建了一个 `SupervisorJob`,然后使用它来创建一个协程作用域。在协程作用域中,我们创建了两个子协程 `job1` 和 `job2`。在 `job1` 中,我们没有使用 `SupervisorScope`,所以在取消时会抛出 `CancellationException` 异常。而在 `job2` 中,我们使用了 `SupervisorScope`,所以在取消时不会抛出异常。 最后,我们通过遍历 `supervisor.children` 来等待所有子协程执行完毕,并输出 "All coroutines completed"。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值