2015-10-26 14 views
7

Tôi nhận được luồng sử dụng asyncio bằng Python 3.5 nhưng tôi chưa thấy mô tả về những thứ tôi nên là await và những điều tôi không nên hoặc không thể bỏ qua. Tôi có phải sử dụng phán đoán tốt nhất của tôi về "đây là một hoạt động IO và do đó nên được await ed"?Khi nào sử dụng và khi nào không sử dụng Python 3.5 `đang chờ?

+4

Đọc [PEP 492] (https://www.python.org/dev/peps/pep-0492/#id50) để biết chi tiết, nhưng nói chung bạn nên "chờ đợi" trên tất cả các tương lai, '@coroutine 'các chức năng được trang trí và các chức năng 'async def'. –

Trả lời

17

Theo mặc định, tất cả mã của bạn đều đồng bộ. Bạn có thể đặt chức năng xác định không đồng bộ với async def và gọi hàm này bằng await. Câu hỏi đúng hơn là "Khi nào tôi nên viết mã không đồng bộ thay vì đồng bộ?". Câu trả lời là "Khi bạn có thể có lợi cho nó". Trong hầu hết các trường hợp như bạn lưu ý bạn sẽ nhận được lợi ích, khi bạn làm việc với I/O hoạt động:

# Synchronous way: 
download(url1) # takes 5 sec. 
download(url2) # takes 5 sec. 
# Total time: 10 sec. 

# Asynchronous way: 
await asyncio.gather(
    download(url1), # takes 5 sec. 
    download(url2) # takes 5 sec. 
) 
# Total time: only 5 sec. (+ little overhead for using asyncio) 

Tất nhiên, nếu bạn đã tạo chức năng có sử dụng mã không đồng bộ, chức năng này nên không đồng bộ quá (nên được định nghĩa là async def). Nhưng bất kỳ chức năng không đồng bộ nào cũng có thể tự do sử dụng mã đồng bộ. Nó làm cho không có ý nghĩa để cast đang đồng bộ để đồng bộ mà không cần một lý do:

# extract_links(url) should be async because it uses async func download() inside 
async def extract_links(url): 
    # download() was created async to get benefit of I/O 
    data = await download(url) 
    # parse() doesn't work with I/O, no sense to make it async 
    links = parse(data) 
    return links 

Một điều rất quan trọng là bất kỳ hoạt động đồng bộ dài (> 50 ms, ví dụ, thật khó để nói chính xác) sẽ đóng băng tất cả không đồng bộ của bạn hoạt động cho thời gian đó:

async def extract_links(url): 
    data = await download(url) 
    links = parse(data) 
    # if search_in_very_big_file() takes much time to process, 
    # all your running async funcs (somewhere else in code) will be friezed 
    # you need to avoid this situation 
    links_found = search_in_very_big_file(links) 

Bạn có thể tránh nó gọi dài chạy chức năng đồng bộ trong quá trình riêng biệt (và chờ kết quả):

executor = ProcessPoolExecutor(2) 

async def extract_links(url): 
    data = await download(url) 
    links = parse(data) 
    # Now your main process can handle another async functions while separate process running  
    links_found = await loop.run_in_executor(executor, search_in_very_big_file, links) 

Một ví dụ khác: khi bạn cần sử dụng requests trong asyncio. requests.get chỉ là chức năng chạy đồng bộ dài, mà bạn không nên gọi bên trong mã async (một lần nữa, để tránh bị đóng băng). Nhưng nó chạy dài vì I/O, không phải vì tính toán dài. Trong trường hợp đó, bạn có thể sử dụng ThreadPoolExecutor thay vì ProcessPoolExecutor để tránh một số overhead đa:

executor = ThreadPoolExecutor(2) 

async def download(url): 
    response = await loop.run_in_executor(executor, requests.get, url) 
    return response.text 
+0

Xin lỗi vì câu trả lời cuối xác nhận. Cảm ơn bạn đã giải thích nó đã giúp tôi rất nhiều! – dalanmiller

-1

Bạn không có nhiều tự do. Nếu bạn cần gọi hàm bạn cần tìm hiểu xem đây có phải là hàm bình thường hay coroutine không. Bạn phải sử dụng từ khóa await nếu và chỉ khi hàm bạn đang gọi là coroutine.

Nếu chức năng async có liên quan, nên có "vòng lặp sự kiện" phối hợp các chức năng async này. Nói đúng là không cần thiết, bạn có thể "tự" chạy phương thức async gửi giá trị cho nó, nhưng có lẽ bạn không muốn làm điều đó. Vòng lặp sự kiện theo dõi các coroutines chưa hoàn thành và chọn tiếp theo để tiếp tục chạy. Mô-đun asyncio cung cấp việc triển khai vòng lặp sự kiện, nhưng đây không phải là triển khai duy nhất có thể thực hiện được.

Hãy xem xét hai dòng sau mã:

x = get_x() 
do_something_else() 

x = await aget_x() 
do_something_else() 

Semantic là hoàn toàn giống nhau: gọi một phương thức trong đó sản xuất một số giá trị, khi giá trị sẵn sàng gán nó vào biến x và làm một việc khác. Trong cả hai trường hợp, hàm do_something_else sẽ chỉ được gọi sau khi dòng mã trước đó kết thúc.Nó thậm chí không có nghĩa là trước hoặc sau hoặc trong khi thực hiện phương thức aget_x không đồng bộ, điều khiển sẽ được mang đến vòng lặp sự kiện.

Tuy nhiên có một số khác biệt:

  • đoạn thứ hai có thể chỉ xuất hiện bên trong một async chức năng
  • aget_x chức năng không phải là bình thường, nhưng coroutine (mà một trong hai được khai báo với async từ khóa hoặc trang trí như coroutine)
  • aget_x có thể "giao tiếp" với vòng lặp sự kiện: mang lại một số đối tượng cho vòng kết nối đó. Vòng lặp sự kiện sẽ có thể giải thích các đối tượng này là yêu cầu để thực hiện một số thao tác (ví dụ: gửi yêu cầu mạng và chờ phản hồi hoặc chỉ tạm dừng coroutine này trong n giây). Chức năng thông thường get_x không thể kết nối với vòng lặp sự kiện.
Các vấn đề liên quan