简体   繁体   English

Python asyncio(aiohttp,aiofiles)

[英]Python asyncio (aiohttp, aiofiles)

I seem to be having a difficult time understanding pythons asyncio. 我似乎很难理解python asyncio。 I have not written any code, as all the examples I see are for one-off runs. 我没有编写任何代码,因为我看到的所有示例都是一次性运行的。 Create a few coroutine's, add them to an event loop, then run the loop, they run the tasks switching between them, done. 创建几个协程,将它们添加到事件循环中,然后运行该循环,它们运行它们之间的任务切换,完成。 Which does not seem all that helpful for me. 这似乎对我没有什么帮助。

I want to use asyncio to not interrupt the operation in my application (using pyqt5). 我想使用asyncio不中断应用程序中的操作(使用pyqt5)。 I want to create some functions that when called run in the asyncio event loop, then when they are done they do a callback. 我想创建一些函数,这些函数在被调用时在asyncio事件循环中运行,然后在完成后执行回调。

What I imagine is. 我想象的是。 Create a separate thread for asyncio, create the loop and run it forever. 为asyncio创建一个单独的线程,创建循环并永久运行。 Create some functions getFile(url, fp) , get(url) , readFile(file) , etc. Then in the UI, I have a text box with a submit button, user enters url, clicks submit, it downloads the file. 创建一些函数getFile(url, fp)get(url)readFile(file)等。然后在UI中,我有一个带有Submit按钮的文本框,用户输入url,单击Submit,然后下载文件。

But, every example I see, I cannot see how to add a coroutine to a running loop. 但是,我看到的每个示例都看不到如何在运行的循环中添加协程。 And I do not see how I could do what I want without adding to a running loop. 而且我看不到不增加运行循环就如何做我想做的事情。

#!/bin/python3
import asyncio
import aiohttp
import threading

loop = asyncio.get_event_loop()

def async_in_thread(loop):
    asyncio.set_event_loop(loop)
    loop.run_forever()

async def _get(url, callback):
    print("get: " + url)
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            result = await response.text()
            callback(result)
            return

def get(url, callback):
    asyncio.ensure_future(_get(url, callback))

thread = threading.Thread(target=async_in_thread, args=(loop, ))

thread.start()

def stop():
    loop.close()

def callme(data):
    print(data)
    stop()

get("http://google.com", callme)

thread.join()

This is what I imagine, but it does not work. 这是我的想象,但没有用。

To add a coroutine to a loop running in a different thread, use asyncio.run_coroutine_threadsafe : 要将协程添加到在其他线程中运行的循环中,请使用asyncio.run_coroutine_threadsafe

def get(url, callback):
    asyncio.run_coroutine_threadsafe(_get(url, callback))

In general, when you are interacting with the event loop from outside the thread that runs it, you must run everything through either run_coroutine_threadsafe (for coroutines) or loop.call_soon_threadsafe (for functions). 通常,从运行事件的线程外部与事件循环进行交互时,必须通过run_coroutine_threadsafe (对于协程)或loop.call_soon_threadsafe (对于函数)来运行所有run_coroutine_threadsafe For example, to stop the loop, use loop.call_soon_threadsafe(loop.stop) . 例如,要停止循环,请使用loop.call_soon_threadsafe(loop.stop) Also note that loop.close() must not be invoked inside a loop callback, so you should place that call in async_in_thread , right after the call to run_forever() , at which point the loop has definitely stopped running. 还要注意,一定不能在循环回调中调用loop.close() ,因此您应该在对run_forever()的调用之后立即将该调用放置在async_in_thread ,这时循环肯定已停止运行。

Another thing with asyncio is that passing explicit when_done callbacks isn't idiomatic because asyncio exposes the concept of futures (akin to JavaScript promises), which allow attaching callbacks to a not-yet-available result. 使用asyncio的另一件事是,传递明确的when_done回调不是惯用的,因为asyncio公开了Future的概念(类似于JavaScript Promise),它允许将回调附加到尚未提供的结果上。 For example, one could write _get like this: 例如,可以这样写_get

async def _get(url):
    print("get: " + url)
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

It doesn't need a callback argument because any interested party can convert it to a task using loop.create_task and use add_done_callback to be notified when the task is complete. 它不需要callback参数,因为任何感兴趣的一方都可以使用loop.create_task将其转换为任务,并使用add_done_callback在任务完成时得到通知。 For example: 例如:

def _get_with_callback(url, callback):
    loop = asyncio.get_event_loop()
    task = loop.create_task(_get(url))
    task.add_done_callback(lambda _fut: callback(task.result()))

In your case you're not dealing with the task directly because your code aims to communicate with the event loop from another thread. 在您的情况下,您不是直接处理任务,因为您的代码旨在与另一个线程的事件循环进行通信。 However, run_coroutine_threadsafe returns a very useful value - a full-fledged concurrent.futures.Future which you can use to register done callbacks. 然而, run_coroutine_threadsafe返回一个非常有用的价值-一个成熟的concurrent.futures.Future你可以用它来注册做过回调。 Instead of accepting a callback argument, you can expose the future object to the caller: 可以不接受callback参数,而可以将Future对象公开给调用者:

def get(url):
    return asyncio.run_coroutine_threadsafe(_get(url), loop)

Now the caller can choose a callback-based approach: 现在,调用者可以选择基于回调的方法:

future = get(url)
# call me when done
future.add_done_callback(some_callback)
# ... proceed with other work ...

or, when appropriate, they can even wait for the result: 或者,在适当的时候,他们甚至可以等待结果:

# give me the response, I'll wait for it
result = get(url).result()

The latter is by definition blocking, but since the event loop is safely running in a different thread, it is not affected by the blocking call. 后者根据定义是阻塞的,但是由于事件循环在另一个线程中安全地运行,因此它不受阻塞调用的影响。

Install QualMash to smooth integration between Qt and asyncio. 安装QualMash以平滑Qt和asyncio之间的集成。

Example from the project's README gives an inspiration for how it looks like: 项目自述文件中的示例为它的外观提供了灵感:

import sys
import asyncio
import time

from PyQt5.QtWidgets import QApplication, QProgressBar
from quamash import QEventLoop, QThreadExecutor

app = QApplication(sys.argv)
loop = QEventLoop(app)
asyncio.set_event_loop(loop)  # NEW must set the event loop

progress = QProgressBar()
progress.setRange(0, 99)
progress.show()

async def master():
    await first_50()
    with QThreadExecutor(1) as exec:
        await loop.run_in_executor(exec, last_50)

async def first_50():
    for i in range(50):
        progress.setValue(i)
        await asyncio.sleep(.1)

def last_50():
    for i in range(50,100):
        loop.call_soon_threadsafe(progress.setValue, i)
        time.sleep(.1)

with loop: ## context manager calls .close() when loop completes, and releases all resources
    loop.run_until_complete(master())

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM