آموزش ماژول asyncio – کنسل کردن coroutine

Please login to bookmark Close

کنسل‌کردن یک coroutine

گاهی یک تسک بیش از حد طول می‌کشد و می‌خواهید آن را متوقف کنید؛ مثلاً دانلودی که خیلی کند شده یا درخواستی که پاسخ نمی‌دهد. در این حالت می‌توانید تسک را با متد cancel کنسل کنید. در این درس سه راه برای کنترل زمان و کنسل‌کردن یک تسک می‌بینیم و یاد می‌گیریم چطور یک تسک را از کنسل‌شدن محافظت کنیم.

روش اول: کنسل دستی با cancel()

در این روش یک تسک می‌سازیم و در یک حلقه وضعیتش را زیر نظر می‌گیریم؛ اگر تا ۵ ثانیه تمام نشد، آن را cancel می‌کنیم. کنسل‌کردن یک تسک باعث می‌شود در نقطه‌ی await جاری، خطای CancelledError پرتاب شود که آن را می‌گیریم:

import asyncio

async def download_file(file_name):
    print(f'download {file_name} started')
    await asyncio.sleep(6)          # simulate a 6-second download
    print(f'download {file_name} finished')

async def manage_downloads():
    t1 = asyncio.create_task(download_file('test1.txt'))

    seconds = 0
    while not t1.done():
        print('download still running')
        await asyncio.sleep(1)
        seconds += 1
        if seconds == 5:
            t1.cancel()            # cancel after 5 seconds

    try:
        await t1
    except asyncio.CancelledError:
        print('download cancelled!')

asyncio.run(manage_downloads())

چون دانلود ۶ ثانیه طول می‌کشد اما ما بعد از ۵ ثانیه کنسلش می‌کنیم، پیام «finished» هرگز چاپ نمی‌شود و به‌جای آن «download cancelled!» را می‌بینیم. توجه کنید که پس از cancel حتماً باید تسک را await کنیم تا CancelledError واقعاً بروز کند و بتوانیم مدیریتش کنیم.

روش دوم و بهتر: wait_for با timeout

نوشتن دستیِ آن حلقه شلوغ و مستعد خطاست. راه تمیزتر، استفاده از asyncio.wait_for است که خودش یک timeout می‌گیرد و اگر تسک در آن مدت تمام نشد، آن را کنسل می‌کند. یک نکته‌ی مهم: wait_for هنگام timeout، خطای asyncio.TimeoutError پرتاب می‌کند (نه CancelledError)؛ پس باید همان را بگیریم:

import asyncio

async def download_file(file_name):
    print(f'download {file_name} started')
    await asyncio.sleep(6)
    print(f'download {file_name} finished')

async def manage_downloads():
    t1 = asyncio.create_task(download_file('test.txt'))

    try:
        await asyncio.wait_for(t1, timeout=5)
    except asyncio.TimeoutError:              # wait_for raises TimeoutError on timeout
        print('download cancelled (timed out)!')

asyncio.run(manage_downloads())

وقتی timeout رخ می‌دهد، wait_for در پشت‌صحنه تسک را کنسل می‌کند و سپس TimeoutError می‌دهد. این کد کوتاه‌تر و خواناتر از روش دستی است.

مثال – محدودکردن زمان یک درخواست شبکه

رایج‌ترین کاربرد واقعیِ این الگو، تحمیل یک «سقف زمانی» به درخواست‌های شبکه است تا برنامه به‌خاطر یک سرور کند برای همیشه معطل نماند. مثلاً «اگر پاسخ API در ۳ ثانیه نرسید، بی‌خیالش شو و یک مقدار پیش‌فرض برگردان»:

import asyncio

async def call_api():
    await asyncio.sleep(10)          # a slow/hanging API (in real code: a network request)
    return {"status": "ok"}

async def get_data_with_timeout():
    try:
        return await asyncio.wait_for(call_api(), timeout=3)
    except asyncio.TimeoutError:
        print("API too slow, using a default value")
        return {"status": "unknown"}

async def main():
    data = await get_data_with_timeout()
    print("result:", data)

asyncio.run(main())

نکته: در پایتون ۳.۱۱ به بعد می‌توانید از context manager تمیزترِ async with asyncio.timeout(3): هم برای همین کار استفاده کنید.

import asyncio

async def call_api():
    await asyncio.sleep(10)          # a slow/hanging API (in real code: a network request)
    return {"status": "ok"}

async def get_data_with_timeout():
    try:
        async with asyncio.timeout(3):
            return await call_api()
    except TimeoutError:
        print("API too slow, using a default value")
        return {"status": "unknown"}

async def main():
    data = await get_data_with_timeout()
    print("result:", data)

asyncio.run(main())

محافظت از یک تسک در برابر کنسل‌شدن با shield

گاهی می‌خواهیم زمان‌سنجی کنیم اما نمی‌خواهیم تسک واقعاً کنسل شود (مثلاً کاری که نصفه‌کاره‌ماندنش خطرناک است). متد asyncio.shield یک coroutine را از کنسل‌شدن محافظت می‌کند؛ اما اگر timeout رخ دهد باز هم TimeoutError می‌گیریم و کنترل از wait_for خارج می‌شود — با این تفاوت که خودِ تسکِ محافظت‌شده در پس‌زمینه به کارش ادامه می‌دهد و در صورت نیاز باید دوباره آن را await کنیم:

import asyncio

async def download_file(file_name):
    print(f'download {file_name} started')
    await asyncio.sleep(6)
    print(f'download {file_name} finished')

async def manage_downloads():
    t1 = asyncio.create_task(download_file('test.txt'))

    try:
        await asyncio.wait_for(asyncio.shield(t1), timeout=5)
    except asyncio.TimeoutError:
        print('downloading takes too long, but we are still working on it')
        await t1        # the shielded task kept running; wait for it to finish

asyncio.run(manage_downloads())

تفاوت با روش قبل: اینجا با اینکه ۵ ثانیه گذشت و TimeoutError گرفتیم، دانلود کنسل نشد و در پس‌زمینه ادامه یافت؛ بعد با await t1 منتظر پایانش ماندیم. کاربرد واقعی shield برای عملیاتی است که نباید نصفه رها شوند — مثل ثبت نهایی یک تراکنش یا ذخیره‌ی یک فایل — که حتی با رسیدن timeout هم باید تا انتها اجرا شوند.

جمع‌بندی

  • یک تسک را با task.cancel() کنسل می‌کنیم؛ سپس با await کردن آن، asyncio.CancelledError را می‌گیریم و مدیریت می‌کنیم.
  • راه تمیزتر برای «سقف زمانی»، asyncio.wait_for(task, timeout=...) است که هنگام timeout تسک را کنسل و asyncio.TimeoutError پرتاب می‌کند.
  • با asyncio.shield(task) می‌توان تسک را از کنسل‌شدن محافظت کرد؛ در این حالت timeout باز هم TimeoutError می‌دهد اما تسک ادامه می‌یابد.
  • کاربرد اصلی این ابزارها، تحمیل سقف زمانی به درخواست‌های شبکه است تا برنامه به‌خاطر یک عملیات کند معلق نماند.

Please login to bookmark Close
پیشرفت شما در «دوره آموزش کانکارنسی در پایتون» (45%)
نظرات

دیدگاهتان را بنویسید

45%
پیشرفت

سرفصل دوره

فهرست مطالب

سرفصل دوره

تمرین

این قسمت تمرین ندارد!

پاسخ تمرین ها

هنوز برای تمرین‌های این قسمت پاسخی ثبت نشده است!

اشتراک گذاری

چرا بهتره از فیلترشکن استفاده کنید؟

من همه ویدئو ها و پادکست های کُدباز رو توی یوتیوب و ساندکلود و پلتفرم هایی آپلود می‌کنم که اغلب فیلتر هستند.

اغلب آموزش‌ها ویدئو و پادکست دارند. پس اگر می‌خواهید از محتوای سایت بیشترین استفاده رو ببرید نیاز به فیلتر شکن دارید.

توجه داشته باشید که برای خرید از فروشگاه بهتره فیلتر شکن رو خاموش کنید.

تنظیمات

انتخاب زبان
تغییر تم