Python中最简单的异步/等待示例

时间:2018-06-08 09:28:04

标签: python python-3.x asynchronous async-await python-asyncio

我在Python 3.5+中阅读了很多关于asyncio / async / await的博客文章,问题/答案,很多很复杂,我发现的最简单的可能是{ {3}}。不过它使用ensure_future,并为学习Python中的异步编程的目的,我想看看甚至更小的例子是可能的(即什么的的最小的必要工具的做基本异步/等待示例)。

问题:为了学习Python中的异步编程,是否可以通过仅使用这两个关键字来提供简单示例,说明async / await如何工作 + asyncio.get_event_loop() + run_until_complete +其他Python代码,但没有其他asyncio函数?

示例:类似这样的事情:

import asyncio

async def async_foo():
    print("async_foo started")
    await asyncio.sleep(5)
    print("async_foo done")

async def main():
    asyncio.ensure_future(async_foo())  # fire and forget async_foo()
    print('Do some actions 1')
    await asyncio.sleep(5)
    print('Do some actions 2')

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

但没有ensure_future,仍然演示了await / async的工作原理。

7 个答案:

答案 0 :(得分:13)

  

是否可以举例说明 exports.sendBigQueryData = functions.analytics.event('buy_from_shop').onLog((event) => { const bigQuery = bigquery({ projectId: }); bigQuery.query({ query: 'select email from table', useLegacySql: false }).then(function (results) { console.log(results); var ref = admin.database().ref("BigQueryData");// this should //create a node with name BigQueryData and store the emails there! var rows = results[0]; //get all fetched table rows rows.forEach(function(row){ //iterate through each row ref.push().set({ email:row['email'] }); }); //return result }); { console.log(email,points); return 0; } }); / async的方式   通过仅使用这两个关键字+ await +来工作   asyncio.get_event_loop() +其他Python代码,但没有其他run_until_complete函数?

这样就可以编写有效的代码:

asyncio

但是这样就无法证明为什么需要asyncio。

顺便说一下,为什么你需要import asyncio async def main(): print('done!') if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main()) ,而不仅仅是简单的代码?答案是 - asyncio允许您在并行化I / O阻塞操作(如读/写网络)时获得性能优势。要编写有用的示例,您需要使用这些操作的异步实现。

请阅读this answer以获取更详细的说明。

<强> UPD:

好的,这是使用asyncio模仿I / O阻塞操作的示例,以及asyncio.sleep,它显示了如何同时运行多个阻塞操作:

asyncio.gather

输出:

import asyncio


async def io_related(name):
    print(f'{name} started')
    await asyncio.sleep(1)
    print(f'{name} finished')


async def main():
    await asyncio.gather(
        io_related('first'),
        io_related('second'),
    )  # 1s + 1s = over 1s


if __name__ ==  '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

注意两个first started second started first finished second finished [Finished in 1.2s] 之后的开始时间,仅在一秒之后完成。

答案 1 :(得分:4)

为回答您的问题,我将为同一问题提供3种不同的解决方案。

情况1:只是普通的python

import time

def sleep():
    print(f'Time: {time.time() - start:.2f}')
    time.sleep(1)

def sum(name, numbers):
    total = 0
    for number in numbers:
        print(f'Task {name}: Computing {total}+{number}')
        sleep()
        total += number
    print(f'Task {name}: Sum = {total}\n')

start = time.time()
tasks = [
    sum("A", [1, 2]),
    sum("B", [1, 2, 3]),
]
end = time.time()
print(f'Time: {end-start:.2f} sec')

输出:

Task A: Computing 0+1
Time: 0.00
Task A: Computing 1+2
Time: 1.00
Task A: Sum = 3

Task B: Computing 0+1
Time: 2.01
Task B: Computing 1+2
Time: 3.01
Task B: Computing 3+3
Time: 4.01
Task B: Sum = 6

Time: 5.02 sec

情况2:异步/等待做错

import asyncio
import time

async def sleep():
    print(f'Time: {time.time() - start:.2f}')
    time.sleep(1)

async def sum(name, numbers):
    total = 0
    for number in numbers:
        print(f'Task {name}: Computing {total}+{number}')
        await sleep()
        total += number
    print(f'Task {name}: Sum = {total}\n')

start = time.time()

loop = asyncio.get_event_loop()
tasks = [
    loop.create_task(sum("A", [1, 2])),
    loop.create_task(sum("B", [1, 2, 3])),
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

end = time.time()
print(f'Time: {end-start:.2f} sec')

输出:

Task A: Computing 0+1
Time: 0.00
Task A: Computing 1+2
Time: 1.00
Task A: Sum = 3

Task B: Computing 0+1
Time: 2.01
Task B: Computing 1+2
Time: 3.01
Task B: Computing 3+3
Time: 4.01
Task B: Sum = 6

Time: 5.01 sec

情况3:异步/等待正确完成(与情况2相同,除了sleep函数之外)

import asyncio
import time

async def sleep():
    print(f'Time: {time.time() - start:.2f}')
    await asyncio.sleep(1)

async def sum(name, numbers):
    total = 0
    for number in numbers:
        print(f'Task {name}: Computing {total}+{number}')
        await sleep()
        total += number
    print(f'Task {name}: Sum = {total}\n')

start = time.time()

loop = asyncio.get_event_loop()
tasks = [
    loop.create_task(sum("A", [1, 2])),
    loop.create_task(sum("B", [1, 2, 3])),
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

end = time.time()
print(f'Time: {end-start:.2f} sec')

输出:

Task A: Computing 0+1
Time: 0.00
Task B: Computing 0+1
Time: 0.00
Task A: Computing 1+2
Time: 1.00
Task B: Computing 1+2
Time: 1.00
Task A: Sum = 3

Task B: Computing 3+3
Time: 2.00
Task B: Sum = 6

Time: 3.01 sec

case 1case 2给出相同的5 seconds,而case 33 seconds。因此async/await done right更快。

差异的原因在于sleep函数的实现。

# case 1
def sleep():
    print(f'Time: {time.time() - start:.2f}')
    time.sleep(1)

# case 2
async def sleep():
    print(f'Time: {time.time() - start:.2f}')
    time.sleep(1)

# case 3
async def sleep():
    print(f'Time: {time.time() - start:.2f}')
    await asyncio.sleep(1)
sleepcase 1中的

case 2函数是“相同的”。 他们“睡觉”而不允许其他人使用资源。 case 3允许在睡眠状态下访问资源。

case 2中,我们在常规函数中添加了async。但是,事件循环将连续运行。 为什么?因为我们没有说允许循环在哪里中断您的函数以运行其他任务。

case 3中,我们告诉事件循环确切的位置是在哪里中断该函数以运行另一个任务。到底在哪里?

# case 3
async def sleep():
    print(f'Time: {time.time() - start:.2f}')
    await asyncio.sleep(1) # <-- Right here!

有关更多内容,请阅读here

答案 2 :(得分:3)

Python 3.7+现在具有a simpler API(在我看来),其措词更简单(比“ ensure_future”更容易记住):您可以使用create_task返回一个Task对象(这很有用)稍后根据需要取消任务)。

基本示例1

import asyncio

async def hello(i):
    print(f"hello {i} started")
    await asyncio.sleep(4)
    print(f"hello {i} done")

async def main():
    task1 = asyncio.create_task(hello(1))  # returns immediately, the task is created
    await asyncio.sleep(3)
    task2 = asyncio.create_task(hello(2))
    await task1
    await task2

asyncio.run(main())  # main loop

结果:

你好1开始
你好2开始
你好1完成了
你好2完成了


基本示例2

如果您需要获取这些异步函数的返回值,那么gather很有用。以下示例的灵感来自documentation,但不幸的是,该文档并未显示gather的真正作用:获取返回值!

import asyncio

async def factorial(n):
    f = 1
    for i in range(2, n + 1):
        print(f"Computing factorial({n}), currently i={i}...")
        await asyncio.sleep(1)
        f *= i
    return f

async def main():
    L = await asyncio.gather(factorial(2), factorial(3), factorial(4))
    print(L)  # [2, 6, 24]

asyncio.run(main())

预期输出:

计算阶乘(2),当前i = 2 ...
正在计算阶乘(3),当前i = 2 ...
正在计算阶乘(4),当前i = 2 ...
正在计算阶乘(3),当前i = 3 ...
正在计算阶乘(4),当前i = 3 ...
正在计算阶乘(4),当前i = 4 ...
[2,6,24]


PS:即使您使用asyncio而不是triothe tutorial of the latter也有助于我完成Python异步编程。

答案 3 :(得分:1)

既然已经很好地解释了所有内容,那么让我们运行一些带有事件循环的示例,将同步代码与异步代码进行比较。

同步代码:

import time

def count():
    time.sleep(1)
    print('1')
    time.sleep(1)
    print('2')
    time.sleep(1)
    print('3')

def main():
    for i in range(3):
        count()

if __name__ == "__main__":
    t = time.perf_counter()
    main()
    t2 = time.perf_counter()
    
    print(f'Total time elapsed: {t2:0.2f} seconds')

输出:

1
2
3
1
2
3
1
2
3
Total time elapsed: 9.00 seconds

我们可以看到每个计数周期都在下一个周期开始之前运行完毕。

异步代码:

import asyncio
import time

async def count():
    await asyncio.sleep(1)
    print('1')
    await asyncio.sleep(1)
    print('2')
    await asyncio.sleep(1)
    print('3')

async def main():
    await asyncio.gather(count(), count(), count())

if __name__ == "__main__":
    t = time.perf_counter()
    asyncio.run(main())
    t2 = time.perf_counter()

    print(f'Total time elapsed: {t2:0.2f} seconds')

输出:

1
1
1
2
2
2
3
3
3
Total time elapsed: 3.00 seconds

另一方面,不同步的声音看起来像是在抽烟,因为这花了三秒钟才能跑完,而不是九秒。 第一个计数周期开始,一旦await进入睡眠状态,一个Python便可以自由地执行其他工作,例如,启动secound和随后的第三个计数周期。 这就是为什么我们拥有所有管子而不是所有三个管子的原因。 在输出中,并发编程可以是非常有价值的工具。 多重处理可以完成所有的多任务处理工作,在Python中,它是多核并发的唯一选择,它可以在多个CPU内核上执行您的程序。 如果使用线程,则操作系统仍在执行所有多任务处理工作,并且在cpython中,全局Intrepeter锁可防止异步编程中的多核并发。 没有操作系统的干预,只有一个进程,只有一个线程,所以运行良好的任务可以在等待期间释放CPU,以便其他任务可以使用它。

import asyncio

loop = asyncio.get_event_loop()


async def greeter(name):
    print(f"Hi, {name} you're in a coroutine.")

try:
    print('starting coroutine')
    coro = greeter('LP')
    print('entering event loop')
    loop.run_until_complete(coro)
finally:
    print('closing event loop')
    loop.close()

输出:

starting coroutine
entering event loop
Hi, LP you're in a coroutine.
closing event loop

异步框架需要一个通常称为事件循环的调度程序。此事件循环跟踪所有正在运行的任务,并且当某个函数挂起时,它将控制权返回给事件循环,该事件循环随后将找到另一个要启动或恢复的函数,这称为协作多任务处理。异步IO提供了一个以该事件循环为中心的异步框架,它可以有效地处理应用程序与事件循环进行交互的输入/输出事件,显式地注册要运行的代码,然后让事件循环使调度程序进行必要的调用资源可用时的应用程序代码。 因此,如果网络服务器打开套接字,然后注册它们,以在发生输入事件时告诉它们,则当有新的传入连接或要读取数据时,事件循环将警告服务器代码。 如果没有比服务器更多的数据要从套接字读取,则将控制权返回给事件循环。

从产生控制权回到事件循环的机制取决于协同例程协同例程是一种为并发操作设计的语言构造。该协同例程可以使用awake关键字与另一个协同例程暂停执行,并且在暂停期间,该协同例程的状态得以维持,从而使其可以从中断的地方继续执行,可以从另一个协同例程开始,然后等待结果,这可以更轻松地将任务分解为可重用的部分。

import asyncio

loop = asyncio.get_event_loop()

async def outer():
    print('in outer')
    print('waiting for result 1')
    result1 = await phase1()
    print('waiting for result 2')
    result2 = await phase2(result1)
    return result1, result2


async def phase1():
    print('in phase1')
    return 'phase1 result'

async def phase2(arg):
    print('in phase2')
    return 'result2 derived from {}'.format(arg)

asyncio.run(outer())

输出:

in outer
waiting for result 1
in phase1
waiting for result 2
in phase2

此示例询问必须按顺序执行但可以与其他操作同时运行的两个阶段。使用awake关键字而不是将新的辅助例程添加到循环中,因为控制流已经在由循环管理的辅助例程内部。不必告诉循环来管理新的协同例程。

答案 4 :(得分:1)

我不知道为什么,但是关于这个主题的所有解释都太复杂了,或者他们使用了无用的 asyncio.sleep() 示例... 到目前为止,我发现的最好的代码示例是:https://codeflex.co/python3-async-await-example/

答案 5 :(得分:0)

public function fetch(Request $req)
{
    
    $firstname = $req->input('firstname');
    $contact=$req->input('contact');
    $bloodgroup=$req->input('bloodgroup');
    $verify= DB::select('select id from patient_details where firstname=? and contact=? and bloodgroup=?',[$firstname,$contact,$bloodgroup]);
    if(count($verify))
    {
        return $this->report($req);
        
    }
    else
    {
        return response(['message' => 'Failed!'], 400);
    }
}
public function report(Request $request)
{
    $firstname = $request->input('firstname');
    $data = DB::table('patient_details')->where('patient_details.firstname','=',$firstname)
    
    ->join('pmedicalreadings','pmedicalreadings.id', '=', 'patient_details.id')
    ->leftjoin('medrecords','medrecords.id','=','patient_details.id')
    ->leftjoin('payments','payments.id','=','patient_details.id')
    ->select('patient_details.firstname','patient_details.lastname','patient_details.address','patient_details.contact','patient_details.bloodgroup','patient_details.dateofbirth','patient_details.cnic','patient_details.gender','pmedicalreadings.bp','pmedicalreadings.temp','pmedicalreadings.ecg','medrecords.prerecord','payments.ptype','payments.tpayment','payments.rpayment')       
    
    ->get();
    return view('report',compact('data'));
}

答案 6 :(得分:0)

似乎每个人都专注于将 time.sleep 转换为 asyncio.sleep,但在现实世界中,这总是不可能的。有时您需要进行库调用,这可能会进行 API 调用(例如:从 google 请求签名 URL)。

以下是您仍然可以使用 time.sleep 的方法,但以异步方式:

import asyncio
import time
from concurrent.futures.thread import ThreadPoolExecutor

def sleep():
    print(f'Time: {time.time() - start:.2f}')
    time.sleep(1)

async def sum(name, numbers):
    _executor = ThreadPoolExecutor(2)
    total = 0
    for number in numbers:
        print(f'Task {name}: Computing {total}+{number}')
        await loop.run_in_executor(_executor, sleep)
        total += number
    print(f'Task {name}: Sum = {total}\n')

start = time.time()

loop = asyncio.get_event_loop()
tasks = [
    loop.create_task(sum("A", [1, 2])),
    loop.create_task(sum("B", [1, 2, 3])),
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

end = time.time()
print(f'Time: {end-start:.2f} sec')

输出:

Task A: Computing 0+1
Time: 0.00
Task B: Computing 0+1
Time: 0.00
Task A: Computing 1+2
Time: 1.00
Task B: Computing 1+2
Time: 1.00
Task A: Sum = 3

Task B: Computing 3+3
Time: 2.01
Task B: Sum = 6

Time: 3.01 sec