Future چیست؟
در asyncio، یک Future شیئی است که نمایندهی یک «نتیجهی آینده» است؛ یعنی مقدار یا استثنایی که هنوز آماده نیست اما قرار است در آینده در دسترس قرار گیرد. میتوانید Future را مثل یک «قبض تحویل» تصور کنید: هنوز خودِ کالا (نتیجه) نرسیده، اما یک برگه دارید که بهمحض آمادهشدن، کالا را با آن تحویل میگیرید. Future یا مقدار دارد (انجامشده / done) یا ندارد (انجامنشده / pending).
ساخت و استفادهی ساده از یک Future
در سادهترین حالت، یک Future میسازیم، نتیجهاش را با set_result تنظیم میکنیم و با result() میخوانیم:
import asyncio
# create a Future object
f = asyncio.Future()
# set the result of the Future
f.set_result(1)
# access the result
print(f, f.result())اما اجرای کد بالا این هشدار را میدهد:
DeprecationWarning: There is no current event loop
<Future pending>
دلیل هشدار این است که ما این Future را در هیچ event loopای نساختهایم؛ در حالی که هر Future باید به یک event loop تعلق داشته باشد. با کمی تغییر و ساختِ Future داخل یک coroutine که با asyncio.run اجرا میشود، مشکل حل میشود:
import asyncio
async def main():
f = asyncio.Future()
f.set_result(1)
print(f, f.result())
asyncio.run(main())روش تمیزترِ ساخت Future، استفاده از خودِ حلقه است: loop = asyncio.get_running_loop() و سپس fut = loop.create_future(). این روشِ توصیهشده است.
منتظرماندن (await) روی یک Future
قدرت واقعی Future وقتی دیده میشود که یک coroutine روی آن await کند و coroutine دیگری نتیجهاش را در آینده تنظیم کند. تا وقتی نتیجه تنظیم نشده، await معلق میماند (بدون مصرف CPU) و بهمحض set_result، ادامه مییابد:
import asyncio
async def set_after(fut, delay, value):
await asyncio.sleep(delay)
fut.set_result(value) # resolve the future later
async def main():
loop = asyncio.get_running_loop()
fut = loop.create_future()
asyncio.create_task(set_after(fut, 1, "done"))
print("waiting for the future...")
result = await fut # suspends until the future is resolved
print("got:", result)
asyncio.run(main())تنظیم خطا با set_exception
یک Future میتواند بهجای مقدار، یک «استثنا» را نگه دارد. اگر با set_exception خطایی روی Future بگذاریم، هنگام await کردن همان خطا پرتاب میشود:
import asyncio
async def main():
loop = asyncio.get_running_loop()
fut = loop.create_future()
fut.set_exception(ValueError("boom"))
try:
await fut
except ValueError as e:
print("caught:", e)
asyncio.run(main())اجرای کاری هنگام آمادهشدن با add_done_callback
میتوانید یک تابع را ثبت کنید تا بهمحض «آمادهشدن» Future (چه با نتیجه، چه با خطا) اجرا شود:
import asyncio
async def main():
loop = asyncio.get_running_loop()
fut = loop.create_future()
fut.add_done_callback(lambda f: print("callback got:", f.result()))
fut.set_result(42)
await asyncio.sleep(0) # give the loop a chance to run the callback
asyncio.run(main())Future در برابر Task
نکتهی مهم: Task در واقع نوع خاصی از Future است که یک coroutine را اجرا میکند و نتیجهاش را خودش تنظیم میکند. یعنی وقتی asyncio.create_task(coro) مینویسید، یک Future میگیرید که بهطور خودکار با پایان آن coroutine «آماده» میشود. به همین دلیل در کد روزمره معمولاً مستقیماً Future نمیسازیم؛ با Task، gather و… کار میکنیم. ساختِ دستی Future بیشتر برای نویسندگان کتابخانه و «پلزدن» کدهای قدیمیِ مبتنی بر callback به دنیای async کاربرد دارد.
مثال واقعی: پلزدن یک API مبتنی بر callback به async
رایجترین کاربرد واقعیِ ساختِ دستیِ Future این است: یک کتابخانهی قدیمی دارید که وقتی داده آماده شد یک «callback» را صدا میزند، اما شما میخواهید بهسبک await با آن کار کنید. یک Future میسازید، در callback نتیجه را روی آن set_result میکنید و در کد async خود آن را await میکنید. اینجا این رفتار را با loop.call_later شبیهسازی میکنیم (که مثل یک callbackِ تأخیری عمل میکند):
import asyncio
async def main():
loop = asyncio.get_running_loop()
fut = loop.create_future()
# imagine a legacy library that invokes a callback when data is ready.
# here we simulate it: after 1 second, the callback resolves the future.
loop.call_later(1, lambda: fut.set_result("data ready"))
print("waiting...")
data = await fut # now we can use it with async/await
print("received:", data)
asyncio.run(main())به این ترتیب، Future مثل یک «پل» بین دو دنیا عمل میکند: کدی که با callback خبر میدهد و کدی که میخواهد با await منتظر بماند. همچنین یک Future میتواند بهعنوان یک «سیگنال یکباره» بین دو coroutine استفاده شود؛ یکی await fut میکند و دیگری هر وقت خواست با fut.set_result(...) آن را بیدار میکند.
جمعبندی
- Future شیئی است که نمایندهی یک نتیجهی آینده (مقدار یا استثنا) است و یا «انجامنشده» است یا «انجامشده».
- نتیجه را با
set_resultو خطا را باset_exceptionتنظیم میکنیم و باresult()یاawaitمیخوانیم. - هر Future باید در یک event loop ساخته شود؛ روش توصیهشده
loop.create_future()است. TaskنوعیFutureاست که یک coroutine را اجرا میکند؛ در کد روزمره معمولاً با Task کار میکنیم و ساخت دستیِ Future بیشتر برای پلزدن کدهای callback-محور است.