• Jingxiao 置顶
    2019-06-25
    发现评论区好多朋友说无法运行,在这里统一解释下:
    1. %time 是 jupyter notebook 自带的语法糖,用来统计一行命令的运行时间;如果你的运行时是纯粹的命令行 python,或者 pycharm,那么请把 %time 删掉,自己用传统的时间戳方法来记录时间也可以;或者使用 jupyter notebook
    2. 我的本地解释器是 Anaconda Python 3.7.3,亲测 windows / ubuntu 均可正常运行,如无法执行可以试试 pip install nest-asyncio,依然无法解决请尝试安装 Anaconda Python
    3. 这次代码因为使用了较新的 API,所以需要较新的版本号,但是朋友们依然出现了一些运行时问题,这里先表示下歉意;同时也想说明的是,在提问之前自己经过充分搜索,尝试后解决问题,带来的快感,和能力的提升,相应也是很大的,一门工程最需要的是 hands on dirty work(动手做脏活),才能让自己的能力得到本质的提升,加油!
    展开
     4
     37
  • Jingxiao 置顶
    2019-07-01
    思考题答案:

    在 python 3.7 及以上的版本中,我们对 task 对象调用 add_done_callback() 函数,即可绑定特定回调函数。回调函数接受一个 future 对象,可以通过 future.result() 来获取协程函数的返回值。

    示例如下:

    import asyncio

    async def crawl_page(url):
        print('crawling {}'.format(url))
        sleep_time = int(url.split('_')[-1])
        await asyncio.sleep(sleep_time)
        return 'OK {}'.format(url)

    async def main(urls):
        tasks = [asyncio.create_task(crawl_page(url)) for url in urls]
        for task in tasks:
            task.add_done_callback(lambda future: print('result: ', future.result()))
        await asyncio.gather(*tasks)

    %time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))

    输出:

    crawling url_1
    crawling url_2
    crawling url_3
    crawling url_4
    result: OK url_1
    result: OK url_2
    result: OK url_3
    result: OK url_4
    Wall time: 4 s
    展开
    
     12
  • Wing·三金
    2019-06-27
    思考题:简单的理解是每个新建的协程对象都会自动调用 add_done_callback() 函数来添加一个回调函数,当协程对象的 Future 状态启动时就会调用该回调函数,从而实现回调。

    综合下前面的留言和个人的学习,总结下 py 3.6 版本下 asyncio 的主要不同:
    1、没有 run(), create_task(),可以用 asyncio.get_even_loop().run_until_complete() 来代替 run(),用 ensure_future() 来代替 create_task();
    2、可能会出现 RuntimeError: This event loop is already running,解决方案一:pip install nest_asyncio; import nest_asyncio; nest_asyncio.apply();解决方案二:有些友人说是 tornado 5.x 起的版本才有此问题,可考虑将其版本降至 4.x(不推荐);
    3、%time 与 %%time 的主要区别:%time func()(必须是同一行);%%time 必须放在单元格的开头,强烈建议单独一行 + 不要与 import、def 相关的语句放在同个单元格;
    4、爬虫中的 aiohttp.ClientSession(headers=header, connector=aiohttp.TCPConnector(ssl=False)) 提及未声明的 header,要么将 headers=header 部分去掉使用默认参数,要么用诸如 header={"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36"} 来显式声明;
    5、tasks = [asyncio.create_task(crawl_page(url)) for url in urls]; await asyncio.gather(*tasks);
    约等于
    tasks = [crawl_page(url) for url in urls]; asyncio.get_even_loop().run_until_complete(asyncio.wait(tasks));
    或
    tasks = [asyncio.ensure_future(crawl_page(url)) for url in urls]; await asyncio.gather(*tasks);
    展开
    
     16
  • cuikt
    2019-06-25
    在代码async with aiohttp.ClientSession(headers=header, connector=aiohttp.TCPConnector(ssl=False)) as session:中的headers=header需要定义header为一个字典可以像下面这样,否则会报错。
    header={"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36"}
    
     14
  • helloworld
    2019-06-27
    说一下我对await的理解:
    开发者要提前知道一个任务的哪个环节会造成I/O阻塞,然后把这个环节的代码异步化处理,并且通过await来标识在任务的该环节中断该任务执行,从而去执行下一个事件循环任务。这样可以充分利用CPU资源,避免CPU等待I/O造成CPU资源白白浪费。当之前任务的那个环节的I/O完成后,线程可以从await获取返回值,然后继续执行没有完成的剩余代码。
    由上面分析可知,如果一个任务不涉及到网络或磁盘I/O这种耗时的操作,而只有CPU计算和内存I/O的操作时,协程并发的性能还不如单线程loop循环的性能高。
     3
     13
  • rogerr
    2019-06-25
    目前看到进阶篇就很痛苦了,对于小白来说,基础篇还好理解,到这儿就对深层次的知识无法深入理解和实践,感觉渐行渐远,不知道如何深入下去
     5
     10
  • tt
    2019-06-24
    学习笔记:异步和阻塞。

    阻塞主要是同步编程中的概念:执行一个系统调用,如果暂时没有返回结果,这个调用就不会返回,那这个系统调用后面的应用代码也不会执行,整个应用被“阻塞”了。

    同步编程也可以有非阻塞的方式,在系统调用没有完成时直接返回一个错误码。

    异步调用是系统调用完成后返回应用一个消息,应用响应消息,获取结果。

    上面说的系统调用 对应Python中的异步函数——一个需要执行较长时间的任务。响应消息应该对应回调函数,但我觉得await异步函数返回本身就相当于系统告知应用系统调用完成了,后面的代码起码可以完成部分回调函数做的事情。

    阻塞和异步必须配合才能完成异步编程。

    1、await异步函数会造成阻塞;
    2、await一个task不会造成阻塞,但是task对应的异步函数中必定几乎await一个异步函数,这个异步函数会阻塞这个task的执行,这样其余task才能被事件调度器的调度从而获取执行机会。
    展开
     2
     5
  • Fergus
    2019-06-27
    # -*- coding:utf-8 -*-
    '''
    sublime
    py -3.6
    asyncio.gather(*args) 并发
    '''


    import time
    import asyncio

    async def crawl_page(url):
        print('crawling {}'.format(url))
        sleep_time = int(url.split('_')[-1])
        await asyncio.sleep(sleep_time)
        print('OK {}'.format(url))
        
    star = time.perf_counter()
    loop = asyncio.get_event_loop()
    tasks = (crawl_page(url) for url in ['url_1', 'url_2', 'url_3', 'url_4'])
    loop.run_until_complete(asyncio.gather(*tasks))
    loop.close()

    print('Wall time: {:.2f}'.format(time.perf_counter() - star))


    crawling url_4
    crawling url_1
    crawling url_2
    crawling url_3
    OK url_1
    OK url_2
    OK url_3
    OK url_4
    Wall time: 4.00
    [Finished in 4.5s]
    展开
     1
     4
  • Fergus
    2019-06-28
    # -*- encoding: utf-8 -*-
    '''
    aiohttp + aysncio爬取电豆瓣电影
    py 3.6
    sublime text3
    '''


    import time
    import aiohttp
    import asyncio
    from bs4 import BeautifulSoup

    now = lambda: time.perf_counter()

    async def fetchHtmlText(url):
        async with aiohttp.ClientSession(
            headers={'users-agent':'Mozilla/5.0'},
            connector=aiohttp.TCPConnector(ssl=False)) as session:
            async with session.get(url) as response:
                return await response.text()

    async def main():
        url = "https://movie.douban.com/cinema/later/beijing/"
        html = await fetchHtmlText(url)
        soup = BeautifulSoup(html, "html.parser")

        divs = soup.find_all('div', class_='item mod')
        urls = list(map(lambda x: x.a.img['src'], divs))
        names = list(map(lambda x: x.h3.a.string, divs))
        dats = list(map(lambda x: x.ul.li.string, divs))

        lis = zip(names, dats, urls)
        for i in lis:
            print("{0:{3}^25} \t {1:{3}^10} \t {2:{3}^}".format(i[0], i[1], i[2],chr(12288)))


    start = now()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    print("Wall time: {}".format(now() - start))


    # 九龙不败                        07月02日   https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2560169035.jpg
    # 别岁                          07月02日   https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2558138041.jpg
    ...
    # 刀背藏身                        07月19日   https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2557644589.jpg
    # Wall time: 1.994356926937086
    # [Finished in 3.7s]
    展开
    
     3
  • xavier
    2019-07-19
    老师你好,请教个问题。如下:

    文中代码:

    async def main():
        task1 = asyncio.create_task(worker_1())
        task2 = asyncio.create_task(worker_2())
        print('before await')
        await task1
        print('awaited worker_1')
        await task2
        print('awaited worker_2')

    请问调度器从何时开始启动调度?是一有任务创建就执行调度还是执行await task1时?老师这里说的是执行await task1,事件调度器开始调度 worker_1。但后面的生产者消费者模型,没有await操作,任务已开始运行。
    展开
     1
     2
  • 某彬
    2019-07-16
    关于以下一段代码:
    ```python
    for task in tasks:
     await task
    ```
    我想问 遇到第一个await之后程序不会跳出整个for循环吗?还是说只会跳出当前的循环?
    展开
    
     2
  • 胡峣
    2019-07-06
    对于熟悉线程模型的程序员来讲,如果不把时间循环解释清楚,协程确实很难理解。
    我的理解协程在编程语言层面实现aio,只有io等待才切换上下文而不像线程按scheduler分片cpu在没必要的时候也切换上下文。不知道我理解的对不对。
    
     2
  • Fergus
    2019-06-27
    # -*- encoding: utf-8 -*-
    '''
    py3.6
    sublime
    '''


    import time
    import asyncio

    now = lambda: time.perf_counter()

    async def work_1():
        await asyncio.sleep(1)
        return 1

    async def work_2():
        await asyncio.sleep(2)
        return 2

    async def work_3():
        await asyncio.sleep(3)
        return 3

    async def main():

        task_1 = asyncio.ensure_future(work_1())
        task_2 = asyncio.ensure_future(work_2())
        task_3 = asyncio.ensure_future(work_3())

        await asyncio.sleep(2)
        task_3.cancel()

        ret = await asyncio.gather(*(task_1, task_2, task_3),
            return_exceptions=True)
        print(ret)

    star = now()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    loop.close()
    print('Wall time:', now() - star)

    # [1, 2, CancelledError()]
    # Wall time: 2.0061719078276914
    # [Finished in 2.5s]
    展开
    
     2
  • Fergus
    2019-06-26
    # 例1 3.7以前的版本实现
    import time
    import asyncio

    # < py 3.7
    async def crawl_page(url):
        print('crawling {}'.format(url))
        sleep_time = int(url.split('_')[-1])
        r = await asyncio.sleep(sleep_time)
        print('OK {}'.format(url))

    async def main(urls):
        for url in urls:
            await crawl_page(url)
            
    star = time.perf_counter()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main(['url_1', 'url_2', 'url_3', 'url_4']))
    loop.close()
    # await main(['url_1', 'url_2', 'url_3', 'url_4'])
    print(time.perf_counter() - star)


    crawling url_1
    OK url_1
    crawling url_2
    OK url_2
    crawling url_3
    OK url_3
    crawling url_4
    OK url_4
    10.003888006104894
    展开
    
     2
  • Fergus
    2019-06-26
    # 例一修改 threading

    import time
    import threading

    def crawl_page(url):
        print('crawling {}'.format(url))
        sleep_time = int(url.split('_')[-1])
        time.sleep(sleep_time)
        print('OK {}'.format(url))
        print('耗时:{:.2f}'.format(time.perf_counter() - star))
        
    def main(urls):
        tasks = [threading.Thread(target=crawl_page, name=url, args=(url,)) for url in urls]
        for t in tasks:
            t.start()
            print(threading.current_thread().name)
        
    star = time.perf_counter()
    main(['url_1', 'url_2', 'url_3', 'url_4'])

    crawling url_1MainThread
    crawling url_2MainThread
    crawling url_3MainThread
    crawling url_4MainThread
    OK url_1
    耗时:1.03
    OK url_2
    耗时:2.06
    OK url_3
    耗时:3.08
    OK url_4
    耗时:4.11
    展开
    
     2
  • 刘朋
    2019-06-26
    当在 jupyter 中运行: %time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))
    出现报错: RuntimeError: asyncio.run() cannot be called from a running event loop
    原因是: The problem in your case is that jupyter (IPython) is already running an event loop (for IPython ≥ 7.0)
    解决是: 将 %time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4'])) 换成 await main(['url_1', 'url_2', 'url_3', 'url_4'])
     2
     2
  • zx钟 💤
    2019-06-24
    官方文档看了好久 还是一脸懵逼。
    
     2
  • 大侠110
    2019-12-03
    感觉还是有很多人看不懂,我试着用通俗的语句讲一下:协成里面重要的是一个关键字await的理解,async表示其修饰的是协程任务即task,await表示的是当线程执行到这一句,此时该task在此处挂起,然后调度器去执行其他的task,当这个挂起的部分处理完,会调用回掉函数告诉调度器我已经执行完了,那么调度器就返回来处理这个task的余下语句。
          
    
     1
  • Geek_57038c
    2019-11-17
    对于这段代码:

    import asyncio

    async def worker_1():
        print('worker_1 start')
        await asyncio.sleep(1)
        print('worker_1 done')

    async def worker_2():
        print('worker_2 start')
        await asyncio.sleep(2)
        print('worker_2 done')

    async def main():
        task1 = asyncio.create_task(worker_1())
        task2 = asyncio.create_task(worker_2())
        print('before await')
        await task1
        print('awaited worker_1')
        await task2
        print('awaited worker_2')

    %time asyncio.run(main())

    ########## 输出 ##########

    before await
    worker_1 start
    worker_2 start
    worker_1 done
    awaited worker_1
    worker_2 done
    awaited worker_2
    Wall time: 2.01 s

    我对老师的解释有些疑问,不知道是不是只要创建了任务以后,不用执行await,就会运行worker_1和worker_2中的代码,因为我注释掉了await语句后,发现仍然会输出worker_1 done和worker_2 done。所有老师的第三步应该不是调度worker_1,而是等待worker_1执行完成?
    展开
    
     1
  • 毛利
    2019-10-31
    asyncio.run() cannot be called from a running event loop
    
     1
我们在线,来聊聊吧