Python 试图实施2“;“线程”;使用'asyncio'模块

Python 试图实施2“;“线程”;使用'asyncio'模块,python,python-3.x,python-asyncio,Python,Python 3.x,Python Asyncio,我以前在Python中使用过线程,但决定尝试一下asyncio模块,特别是因为您可以取消正在运行的任务,这看起来是一个很好的细节。然而,由于某种原因,我不能把我的头围绕着它 以下是我想要实现的内容(如果我使用了错误的术语,很抱歉): 一个downloader线程,它每x秒下载一个相同的文件,对照上一次下载检查其哈希值,如果不同,则保存它 在后台运行的webserver线程,允许控制downloader线程(暂停、列出、停止) 我对Web服务器使用了aiohttp 这就是我到目前为止所做的:

我以前在Python中使用过线程,但决定尝试一下
asyncio
模块,特别是因为您可以取消正在运行的任务,这看起来是一个很好的细节。然而,由于某种原因,我不能把我的头围绕着它

以下是我想要实现的内容(如果我使用了错误的术语,很抱歉):

  • 一个
    downloader
    线程,它每x秒下载一个相同的文件,对照上一次下载检查其哈希值,如果不同,则保存它
  • 在后台运行的
    webserver
    线程,允许控制
    downloader
    线程(暂停、列出、停止)
我对Web服务器使用了
aiohttp

这就是我到目前为止所做的:

class aiotest():

    def __init__(self):
        self._dl = None     # downloader future
        self._webapp = None # web server future
        self.init_server()

    def init_server(self):

        print('Setting up web interface')
        app = web.Application()
        app.router.add_route('GET', '/stop', self.stop)
        print('added urls')
        self._webapp = app

    @asyncio.coroutine
    def _downloader(self):
        while True:
            try:
                print('Downloading and verifying file...')
                # Dummy sleep - to be replaced by actual code
                yield from asyncio.sleep(random.randint(3,10))
                # Wait a predefined nr of seconds between downloads
                yield from asyncio.sleep(30)
            except asyncio.CancelledError:
                break

    @asyncio.coroutine
    def _supervisor(self):

        print('Starting downloader')
        self._dl = asyncio.async(self._downloader())

    def start(self):
        loop = asyncio.get_event_loop()
        loop.run_until_complete(self._supervisor())
        loop.close()

    @asyncio.coroutine
    def stop(self):
        print('Received STOP')
        self._dl.cancel()
        return web.Response(body=b"Stopping... ")
此类由以下人员调用:

t = aiotest()
t.start()
这当然不行,我觉得这是一段可怕的代码

我不清楚的是:

  • 我在
    stop()
    方法中停止
    downloader
    ,但是如何停止Web服务器(例如在
    shutdown()
    方法中)
  • 下载程序是否需要一个新的事件循环,或者我是否可以使用
    asyncio.get_event_loop()
    返回的循环
  • 我真的需要类似于
    主管的东西来实现我想要实现的东西吗?这看起来很笨重。我如何让
    supervisor
    继续运行,而不是像现在这样执行一次就结束
最后一个更一般的问题是:
asyncio
是否应该取代
threading
模块(将来)?或者每个应用程序都有自己的应用程序


我感谢所有的提示、评论和澄清

当前代码不起作用的原因:

  • 您正在运行事件循环,直到
    self.\u supervisor()
    完成<代码>自我。_supervisor()
    创建任务(立即发生)并立即完成

  • 您正在尝试运行事件循环,直到
    \u supervisor
    完成,但如何以及何时启动服务器?我认为事件循环应该一直运行,直到服务器停止<代码>\u主管
    或其他内容可以作为任务添加(到同一事件循环)
    aiohttp
    已经具有启动服务器和事件循环的功能-
    web。运行应用程序,但我们可以做到

你的问题:

  • 您的服务器将一直运行,直到您停止它。您可以启动/停止不同的操作 在服务器工作时执行协同路由

  • 不同的协程只需要一个事件循环

  • 我想你不需要
    主管

  • 更一般的问题:
    asyncio
    帮助您运行不同的 函数在单个进程的单个线程中并行。这就是为什么 asyncio是如此的酷和快速。你的一些同步代码与你的线程 可以使用asyncio及其协程重写。此外,asyncio可以 使用线程和进程。 在您仍然需要线程和进程的情况下,它会很有用:下面是一个例子

  • 实用说明:

    • 当我们讨论非线程的异步协程时,最好使用术语
      coroutine
      而不是
      thread
    • 如果使用Python 3.5,则可以使用
      async
      /
      wait
      而不是
      coroutine
      /
    我重写了你的代码,向你展示我的想法。如何检查:运行程序,参见控制台,打开
    http://localhost:8080/stop
    ,请参阅控制台,打开
    http://localhost:8080/start
    ,请参见控制台,键入CTRL+C

    import asyncio
    import random
    from contextlib import suppress
    
    from aiohttp import web
    
    
    class aiotest():
        def __init__(self):
            self._webapp = None
            self._d_task = None
            self.init_server()
    
        # SERVER:
        def init_server(self):
            app = web.Application()
            app.router.add_route('GET', '/start', self.start)
            app.router.add_route('GET', '/stop', self.stop)
            app.router.add_route('GET', '/kill_server', self.kill_server)
            self._webapp = app
    
        def run_server(self):
            # Create server:
            loop = asyncio.get_event_loop()
            handler = self._webapp.make_handler()
            f = loop.create_server(handler, '0.0.0.0', 8080)
            srv = loop.run_until_complete(f)
            try:
                # Start downloader at server start:
                asyncio.async(self.start(None))  # I'm using controllers here and below to be short,
                                                 # but it's better to split controller and start func
                # Start server:
                loop.run_forever()
            except KeyboardInterrupt:
                pass
            finally:
                # Stop downloader when server stopped:
                loop.run_until_complete(self.stop(None))
                # Cleanup resources:
                srv.close()
                loop.run_until_complete(srv.wait_closed())
                loop.run_until_complete(self._webapp.shutdown())
                loop.run_until_complete(handler.finish_connections(60.0))
                loop.run_until_complete(self._webapp.cleanup())
            loop.close()
    
        @asyncio.coroutine
        def kill_server(self, request):
            print('Server killing...')
            loop = asyncio.get_event_loop()
            loop.stop()
            return web.Response(body=b"Server killed")
    
        # DOWNLOADER
        @asyncio.coroutine
        def start(self, request):
            if self._d_task is None:
                print('Downloader starting...')
                self._d_task = asyncio.async(self._downloader())
                return web.Response(body=b"Downloader started")
            else:
                return web.Response(body=b"Downloader already started")
    
        @asyncio.coroutine
        def stop(self, request):
            if (self._d_task is not None) and (not self._d_task.cancelled()):
                print('Downloader stopping...')
                self._d_task.cancel()            
                # cancel() just say task it should be cancelled
                # to able task handle CancelledError await for it
                with suppress(asyncio.CancelledError):
                    yield from self._d_task
                self._d_task = None
                return web.Response(body=b"Downloader stopped")
            else:
                return web.Response(body=b"Downloader already stopped or stopping")
    
        @asyncio.coroutine
        def _downloader(self):
            while True:
                print('Downloading and verifying file...')
                # Dummy sleep - to be replaced by actual code
                yield from asyncio.sleep(random.randint(1, 2))
                # Wait a predefined nr of seconds between downloads
                yield from asyncio.sleep(1)
    
    
    if __name__ == '__main__':
        t = aiotest()
        t.run_server()
    

    您的
    下载程序是否像在传统线程中一样阻塞?或者它会使用所有异步调用吗?@cpburnz我最初使用的是
    请求
    (如果我读得正确,它会被阻塞),但也可以使用
    aiohttp
    。它不会下载多个文件(只有一个),而且文件本身相对较小(<200KB)。@Kristof关于答案,你有什么问题吗?尽管问吧。@German你真的帮了我很多忙,我已经在
    asyncio
    上读了不少书,慢慢地开始掌握窍门了。非常感谢您花时间重写我的代码!如果我想通过HTTP调用关闭服务器(并完全停止脚本),就像我使用
    CTRL-C
    中断一样,我该怎么办?我的每一次尝试都会导致
    运行时错误:事件循环正在运行。
    @Kristof您的服务器是处理请求的事件循环。您可以运行服务器使其永远工作,但可以使用
    键盘中断来停止它。如果您想在另一个协同程序或函数中停止它,还可以调用
    loop.close()
    -它将执行与键入CTRL+C时几乎相同的操作。例如,现在您可以使用
    停止服务器http://localhost:8080/kill_server
    。还有一个注意事项:我知道最好将downloader stop放置在
    finally
    块中(只要您打算以不同的方式停止服务器)。