代码之家  ›  专栏  ›  技术社区  ›  J. Taylor

如何在异步中并发运行任务?

  •  3
  • J. Taylor  · 技术社区  · 7 年前

    我正在尝试学习如何使用Python的异步模块并发地运行任务。在下面的代码中,我举了一个模拟的“网络爬虫”。基本上,我尝试在任何给定时间最多发生两个活动fetch()请求的情况下进行,我希望在sleep()期间调用process()。

    import asyncio
    
    class Crawler():
    
        urlq = ['http://www.google.com', 'http://www.yahoo.com', 
                'http://www.cnn.com', 'http://www.gamespot.com', 
                'http://www.facebook.com', 'http://www.evergreen.edu']
    
        htmlq = []
        MAX_ACTIVE_FETCHES = 2
        active_fetches = 0
    
        def __init__(self):
            pass
    
        async def fetch(self, url):
            self.active_fetches += 1
            print("Fetching URL: " + url);
            await(asyncio.sleep(2))
            self.active_fetches -= 1
            self.htmlq.append(url)
    
        async def crawl(self):
            while self.active_fetches < self.MAX_ACTIVE_FETCHES:
                if self.urlq:
                    url = self.urlq.pop()
                    task = asyncio.create_task(self.fetch(url))
                    await task
                else:
                    print("URL queue empty")
                    break;
    
        def process(self, page):
            print("processed page: " + page)
    
    # main loop
    
    c = Crawler()
    while(c.urlq):
        asyncio.run(c.crawl())
        while c.htmlq:
            page = c.htmlq.pop()
            c.process(page)
    

    但是,上面的代码一个接一个地下载URL(一次不同时下载两个),并且直到所有URL都被获取之后才进行任何“处理”。如何使fetch()任务并发运行,并使其在sleep()期间在其间调用process()?

    2 回复  |  直到 7 年前
        1
  •  2
  •   dtanabe    7 年前

    你的 crawl 方法正在等待每个单独的任务;您应该将其更改为:

    async def crawl(self):
        tasks = []
        while self.active_fetches < self.MAX_ACTIVE_FETCHES:
            if self.urlq:
                url = self.urlq.pop()
                tasks.append(asyncio.create_task(self.fetch(url)))
        await asyncio.gather(*tasks)
    

    编辑 :这里有一个更清晰的版本,注释可以同时提取和处理所有内容,同时保留对最大提取数设置上限的基本能力。

    import asyncio
    
    class Crawler:
    
        def __init__(self, urls, max_workers=2):
            self.urls = urls
            # create a queue that only allows a maximum of two items
            self.fetching = asyncio.Queue()
            self.max_workers = max_workers
    
        async def crawl(self):
            # DON'T await here; start consuming things out of the queue, and
            # meanwhile execution of this function continues. We'll start two
            # coroutines for fetching and two coroutines for processing.
            all_the_coros = asyncio.gather(
                *[self._worker(i) for i in range(self.max_workers)])
    
            # place all URLs on the queue
            for url in self.urls:
                await self.fetching.put(url)
    
            # now put a bunch of `None`'s in the queue as signals to the workers
            # that there are no more items in the queue.
            for _ in range(self.max_workers):
                await self.fetching.put(None)
    
            # now make sure everything is done
            await all_the_coros
    
        async def _worker(self, i):
            while True:
                url = await self.fetching.get()
                if url is None:
                    # this coroutine is done; simply return to exit
                    return
    
                print(f'Fetch worker {i} is fetching a URL: {url}')
                page = await self.fetch(url)
                self.process(page)
    
        async def fetch(self, url):
            print("Fetching URL: " + url);
            await asyncio.sleep(2)
            return f"the contents of {url}"
    
        def process(self, page):
            print("processed page: " + page)
    
    
    # main loop
    c = Crawler(['http://www.google.com', 'http://www.yahoo.com', 
                 'http://www.cnn.com', 'http://www.gamespot.com', 
                 'http://www.facebook.com', 'http://www.evergreen.edu'])
    asyncio.run(c.crawl())
    
        2
  •  1
  •   user4815162342    7 年前

    你可以做 htmlq asyncio.Queue() 和改变 htmlq.append htmlq.push . 那么你的 main 可以是异步的,如下所示:

    async def main():
        c = Crawler()
        asyncio.create_task(c.crawl())
        while True:
            page = await c.htmlq.get()
            if page is None:
                break
            c.process(page)
    

    您的顶级代码可以归结为调用 asyncio.run(main()) .

    一旦你完成了爬行, crawl() 可以入队 None 通知主协同工作已完成。