در این فصل، بهصورت کامل و مستقل به بررسی مدیریت خطا در محیط threading میپردازیم. فرض میکنیم که شما با مفاهیم پایهی threading آشنایی دارید و قصد دارید برنامههایی مقاوم و قابلاعتماد با استفاده از تردها بنویسید.
چالشهای منحصربهفرد threading در مدیریت خطا
threading در پایتون، با وجود سادگی، چالشهای خاص خود را در مدیریت خطا دارد. بیایید ابتدا این چالشها را بشناسیم:
۱. عدم امکان قطع اجباری ترد
برخلاف asyncio که میتوانید یک تسک را با cancel() متوقف کنید، در threading هیچ راهکار توکاری برای متوقف کردن یک ترد از بیرون وجود ندارد. متد thread.join(timeout) فقط منتظر میماند و پس از اتمام زمان، اجرا را ادامه میدهد، اما خود ترد همچنان به کار خود ادامه میدهد.
۲. اشتراکگذاری حافظه
تردها در پایتون حافظهی مشترک دارند. این ویژگی باعث میشود خطاهای مربوط به شرایط رقابتی (Race Condition) و تخریب دادهها، در threading شایعتر از سایر رویکردها باشند.
۳. مدیریت استثناها
اگر یک استثنا در یک ترد رخ دهد و مدیریت نشود، آن ترد از کار میافتد، اما سایر تردها و برنامهی اصلی به کار خود ادامه میدهند. این یعنی ممکن است خطا بهطور کامل نادیده گرفته شود.
۴. تشخیص وضعیت تردها
بررسی اینکه آیا یک ترد هنوز در حال اجراست یا متوقف شده، نیازمند استفاده از متد is_alive() است. اما این متد اطلاعاتی دربارهی علت توقف ترد (پایان عادی یا خطا) به ما نمیدهد.
الگوی Timeout در threading
همانطور که گفته شد، قطع کردن اجباری یک ترد در پایتون ممکن نیست. اما روشهای مختلفی برای شبیهسازی Timeout و مدیریت عملیاتهای طولانی وجود دارد.
روش اول: استفاده از threading.Timer
سناریوی واقعی: فرض کنید در یک برنامهی پردازش تصویر، یک ترد را برای اعمال فیلتر روی یک تصویر بزرگ راهاندازی کردهاید. اگر این عملیات بیش از ۱۰ ثانیه طول بکشد، میخواهید به کاربر اطلاع دهید و اجازه دهید تصمیم بگیرد که آیا منتظر بماند یا عملیات را لغو کند.
import threading
import time
def apply_image_filter():
print("applying filter to image...")
time.sleep(10)
print("filter applied successfully")
def timeout_handler():
print("timeout occurred! image processing is taking too long.")
print("you can choose to wait or cancel the operation.")
timer = threading.Timer(5.0, timeout_handler)
timer.start()
thread = threading.Thread(target=apply_image_filter)
thread.start()
thread.join()
if thread.is_alive():
print("thread is still running despite timeout")
else:
print("thread finished")
timer.cancel()نکتهی مهم: همانطور که در خروجی میبینید، Timer فقط اعلام میکند که زمان به پایان رسیده، اما ترد اصلی همچنان به کار خود ادامه میدهد. اگر میخواهیم عملیات را واقعاً متوقف کنیم، باید از روشهای هماهنگی استفاده کنیم.
روش دوم: استفاده از threading.Event برای هماهنگی
سناریوی واقعی: یک برنامهی دانلود فایل دارید که فایلهای بزرگ را از سرور دریافت میکند. اگر سرعت اینترنت پایین باشد، دانلود ممکن است بیش از حد طول بکشد. میخواهید اگر دانلود بیش از ۳۰ ثانیه طول کشید، به کاربر امکان لغو دانلود را بدهید.
import threading
import time
def download_file(stop_event):
print("download started")
for chunk in range(10):
if stop_event.is_set():
print("download cancelled by user")
return "cancelled"
time.sleep(1)
print(f"downloaded chunk {chunk + 1}/10")
print("download completed successfully")
return "file_content"
stop_event = threading.Event()
thread = threading.Thread(target=download_file, args=(stop_event,))
thread.start()
time.sleep(3)
user_choice = input("download taking too long. Cancel? (y/n): ")
if user_choice.lower() == 'y':
stop_event.set()
print("cancellation signal sent")
thread.join()
print("thread finished")مزایا:
- ترد بهصورت خودکار و ایمن متوقف میشود.
- هیچ منبعی بهصورت اجباری آزاد نمیشود.
- امکان انجام عملیات پاکسازی قبل از خروج وجود دارد.
معایب:
- نیاز به تغییر کد تابع برای چک کردن دورهای
Eventدارد. - در عملیاتهای blocking (مانند
socket.recv) نمیتوان از این روش استفاده کرد.
روش سوم: استفاده از concurrent.futures.ThreadPoolExecutor
سناریوی واقعی: یک API سرویس پرداخت آنلاین دارید که باید نتیجهی تراکنش را در کمتر از ۲ ثانیه برگرداند. اگر سرویس پرداخت کند کار کند یا قطع شود، باید به کاربر اعلام کنید که تراکنش ناموفق بوده و دوباره تلاش کند.
from concurrent.futures import ThreadPoolExecutor, TimeoutError
import time
import random
def payment_gateway_api():
print("connecting to payment gateway...")
time.sleep(random.uniform(0.5, 3.0))
if random.random() < 0.2:
raise ValueError("payment gateway returned error")
return "transaction_successful"
def process_payment(timeout_seconds=2.0):
with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(payment_gateway_api)
try:
result = future.result(timeout=timeout_seconds)
print(f"payment result: {result}")
return result
except TimeoutError:
print("payment gateway timed out - transaction pending")
future.cancel()
print("please check transaction status later")
return "pending"
except Exception as e:
print(f"payment failed: {e}")
return "failed"
result = process_payment(2.0)
print(f"final status: {result}")نکته: future.cancel() در ThreadPoolExecutor تضمینی برای توقف واقعی ترد نیست. اگر ترد در حال اجرا باشد، cancel() تأثیری ندارد. اما اگر ترد هنوز شروع نشده باشد، از اجرای آن جلوگیری میکند.
روش چهارم: ترکیب Event و ThreadPoolExecutor
سناریوی واقعی: یک ربات معاملاتی خودکار دارید که باید قیمت لحظهای ارزهای دیجیتال را از چندین صرافی دریافت کند. هر صرافی ممکن است کند پاسخ دهد. میخواهید اگر یک صرافی بیش از ۱ ثانیه پاسخ نداد، از آن صرف نظر کنید و از سایر صرافیها استفاده کنید.
from concurrent.futures import ThreadPoolExecutor, TimeoutError
import threading
import time
import random
def fetch_exchange_price(exchange_name, stop_event):
print(f"fetching price from {exchange_name}...")
for step in range(5):
if stop_event.is_set():
print(f"price fetch from {exchange_name} cancelled")
return None
time.sleep(0.3)
if random.random() < 0.3:
time.sleep(2.0)
return f"{exchange_name}_price_$100"
def get_market_prices(max_wait=1.0):
exchanges = ["binance", "coinbase", "kraken"]
stop_event = threading.Event()
prices = {}
with ThreadPoolExecutor(max_workers=len(exchanges)) as executor:
futures = {
executor.submit(fetch_exchange_price, exchange, stop_event): exchange
for exchange in exchanges
}
for future in futures:
exchange = futures[future]
try:
price = future.result(timeout=max_wait)
if price:
prices[exchange] = price
except TimeoutError:
print(f"timeout getting price from {exchange}")
stop_event.set()
return prices
prices = get_market_prices(1.0)
print(f"market prices: {prices}")الگوی Retry در threading
پیادهسازی Retry در threading بسیار شبیه به حالت عادی است، اما بهجای asyncio.sleep از time.sleep استفاده میکنیم.
Retry با تأخیر ثابت (Fixed Delay)
سناریوی واقعی: یک برنامهی ارسال ایمیل انبوه دارید. گاهی سرور ایمیل بهدلیل شلوغی، خطای موقت برمیگرداند. میخواهید در صورت بروز خطای موقت، تا ۳ بار با فاصلهی ۲ ثانیه دوباره تلاش کنید.
import time
import random
def send_email(recipient):
print(f"sending email to {recipient}...")
if random.random() < 0.6:
raise ConnectionError("smtp server temporarily unavailable")
print(f"email sent to {recipient} successfully")
return "email_sent"
def retry_fixed_delay(operation, *args, max_retries=3, delay=2.0):
for attempt in range(max_retries):
try:
print(f"attempt {attempt + 1} for {args[0]}")
result = operation(*args)
return result
except Exception as e:
print(f"attempt {attempt + 1} failed: {e}")
if attempt == max_retries - 1:
print(f"all attempts failed for {args[0]}")
raise
print(f"waiting {delay} seconds before retry...")
time.sleep(delay)
recipients = ["user1@example.com", "user2@example.com"]
for recipient in recipients:
try:
result = retry_fixed_delay(send_email, recipient, max_retries=3, delay=2.0)
print(f"final result for {recipient}: {result}")
except Exception as e:
print(f"email to {recipient} failed completely: {e}")Retry با تأخیر تصاعدی (Exponential Backoff)
سناریوی واقعی: یک سرویس تحلیل دادههای مالی دارید که هر ساعت، دادههای جدید را از یک API خارجی دریافت میکند. API خارجی گاهی با حجم درخواستها سنگین میشود. با Exponential Backoff، در صورت خطا، مدت انتظار را افزایش میدهید تا به سرور فشار نیاورید.
import time
import random
def fetch_financial_data():
print("fetching financial data...")
if random.random() < 0.5:
raise ConnectionError("data provider service busy")
time.sleep(0.5)
return {"price": 100, "volume": 1000}
def retry_exponential_backoff(operation, max_retries=5, base_delay=1.0):
for attempt in range(max_retries):
try:
print(f"attempt {attempt + 1} started")
result = operation()
print(f"attempt {attempt + 1} succeeded")
return result
except Exception as e:
print(f"attempt {attempt + 1} failed: {e}")
if attempt == max_retries - 1:
print("all attempts failed - service might be down")
raise
delay = base_delay * (2 ** attempt)
jitter = random.uniform(0, delay * 0.2)
total_delay = delay + jitter
print(f"waiting {total_delay:.2f} seconds before retry...")
time.sleep(total_delay)
try:
data = retry_exponential_backoff(fetch_financial_data, max_retries=4)
print(f"final data: {data}")
except Exception as e:
print(f"data fetch failed: {e}")Retry با شرط (Conditional Retry)
سناریوی واقعی: یک سیستم پردازش سفارش در فروشگاه اینترنتی دارید. اگر خطای مربوط به موجودی کالا رخ دهد (کالا تمام شده)، دیگر تلاش مجدد فایدهای ندارد. اما اگر خطای شبکه یا خطای موقت سرور باشد، میتوانید دوباره تلاش کنید.
import time
import random
class RetryableError(Exception):
pass
class FatalError(Exception):
pass
def process_order(order_id):
rand = random.random()
if rand < 0.3:
raise RetryableError("payment gateway temporarily unavailable")
elif rand < 0.6:
raise FatalError("product out of stock")
return f"order_{order_id}_processed"
def retry_on_specific_errors(operation, *args, max_retries=3):
for attempt in range(max_retries):
try:
print(f"attempt {attempt + 1} for order {args[0]}")
result = operation(*args)
print(f"attempt {attempt + 1} succeeded")
return result
except RetryableError as e:
print(f"retryable error on attempt {attempt + 1}: {e}")
if attempt == max_retries - 1:
print("max retries reached, marking order as pending")
raise
delay = 1.0 * (2 ** attempt)
time.sleep(delay)
except FatalError as e:
print(f"fatal error, stopping retry: {e}")
print("order needs manual intervention")
raise
except Exception as e:
print(f"unexpected error: {e}")
raise
orders = ["ORD-1001", "ORD-1002", "ORD-1003"]
for order in orders:
try:
result = retry_on_specific_errors(process_order, order, max_retries=3)
print(f"order {order}: {result}")
except FatalError:
print(f"order {order}: failed permanently")
except RetryableError:
print(f"order {order}: pending - will retry later")
except Exception as e:
print(f"order {order}: unexpected error: {e}")ترکیب Timeout و Retry در threading
سناریوی واقعی: یک اپلیکیشن موبایل دارید که با سرور مرکزی همگامسازی میشود. هر بار همگامسازی، باید اطلاعات کاربر را از سرور دریافت کند. سرور گاهی کند میشود یا قطع میشود. میخواهید تا ۳ بار تلاش کنید و هر بار حداکثر ۲ ثانیه صبر کنید.
from concurrent.futures import ThreadPoolExecutor, TimeoutError
import time
import random
def sync_with_server():
delay = random.uniform(0.5, 4.0)
print(f"sync will take {delay:.2f} seconds")
time.sleep(delay)
if random.random() < 0.3:
raise ValueError("server returned invalid data")
return "user_data_synced"
def robust_sync(max_retries=3, timeout_per_attempt=2.0):
for attempt in range(max_retries):
print(f"\nsync attempt {attempt + 1} started")
with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(sync_with_server)
try:
result = future.result(timeout=timeout_per_attempt)
print(f"attempt {attempt + 1} succeeded")
return result
except TimeoutError:
print(f"attempt {attempt + 1} timed out")
future.cancel()
except Exception as e:
print(f"attempt {attempt + 1} failed with error: {e}")
future.cancel()
if attempt == max_retries - 1:
print("all sync attempts failed")
raise Exception("sync failed after all retries")
delay = 0.5 * (2 ** attempt)
print(f"waiting {delay} seconds before next attempt...")
time.sleep(delay)
try:
result = robust_sync(max_retries=4, timeout_per_attempt=2.0)
print(f"sync completed: {result}")
except Exception as e:
print(f"sync failed: {e}")
print("showing cached data to user...")مدیریت استثناها در تردها
یکی از چالشهای مهم در threading، مدیریت استثناهایی است که در تردها رخ میدهند. اگر استثنا در ترد مدیریت نشود، آن ترد از کار میافتد اما برنامهی اصلی متوقف نمیشود.
روش اول: استفاده از threading.excepthook
سناریوی واقعی: یک اپلیکیشن مانیتورینگ دارید که ۱۰۰ ترد برای بررسی وضعیت سرورهای مختلف اجرا میکند. اگر یکی از تردها بهدلیل خطای غیرمنتظره از کار بیفتد، میخواهید بدانید و خطا را لاگ کنید تا بتوانید مشکل را ردیابی کنید.
import threading
import time
import logging
logging.basicConfig(level=logging.INFO)
def exception_handler(args):
thread_name = args.thread.name
exc_type = args.exc_type.__name__
exc_value = args.exc_value
print(f"ERROR in thread '{thread_name}': {exc_type}: {exc_value}")
logging.error(f"Thread '{thread_name}' crashed: {exc_type}: {exc_value}")
threading.excepthook = exception_handler
def monitor_server(server_name):
print(f"monitoring {server_name}...")
time.sleep(1)
if server_name == "server-3":
raise ValueError(f"unexpected response from {server_name}")
print(f"{server_name} is healthy")
servers = ["server-1", "server-2", "server-3", "server-4"]
threads = []
for server in servers:
thread = threading.Thread(target=monitor_server, args=(server,))
thread.name = f"Monitor-{server}"
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
print("monitoring cycle completed")روش دوم: برگرداندن استثنا با Queue
سناریوی واقعی: یک سیستم پردازش تصویر دارید که چندین تصویر را همزمان پردازش میکند. اگر پردازش یک تصویر با خطا مواجه شود، میخواهید نتیجهی آن را به ترد اصلی گزارش دهید تا مشخص شود کدام تصویر پردازش نشده است.
import threading
import time
import queue
def process_image(image_path, result_queue):
try:
print(f"processing {image_path}...")
time.sleep(1)
if "corrupted" in image_path:
raise ValueError(f"image {image_path} is corrupted")
if "large" in image_path:
raise MemoryError(f"image {image_path} is too large")
result_queue.put(f"processed_{image_path}")
except Exception as e:
result_queue.put(e)
def batch_process_images(image_paths):
result_queue = queue.Queue()
threads = []
results = {}
for path in image_paths:
thread = threading.Thread(
target=process_image,
args=(path, result_queue)
)
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
while not result_queue.empty():
result = result_queue.get()
if isinstance(result, Exception):
print(f"error during processing: {result}")
else:
print(f"success: {result}")
results[result] = "processed"
return results
images = [
"image1.jpg",
"image2_corrupted.jpg",
"image3.jpg",
"image4_large.jpg"
]
results = batch_process_images(images)
print(f"processed {len(results)} images successfully")روش سوم: استفاده از concurrent.futures برای دریافت استثنا
سناریوی واقعی: یک ربات تلگرام دارید که همزمان به چندین کاربر پاسخ میدهد. اگر پاسخ به یک کاربر با خطا مواجه شود، نباید روی پاسخ به سایر کاربران تأثیر بگذارد. با استفاده از Future، استثناها بهطور خودکار به ترد اصلی منتقل میشوند و میتوانید تصمیم بگیرید که چه کاری انجام دهید.
from concurrent.futures import ThreadPoolExecutor
import time
import random
def handle_user_request(user_id):
print(f"handling request from user {user_id}...")
time.sleep(random.uniform(0.5, 1.5))
if user_id == 1002:
raise ValueError(f"user {user_id} has invalid token")
if user_id == 1005:
raise ConnectionError(f"cannot connect to user {user_id}")
return f"response_for_user_{user_id}"
def process_users(user_ids):
with ThreadPoolExecutor(max_workers=3) as executor:
futures = {
executor.submit(handle_user_request, user_id): user_id
for user_id in user_ids
}
results = {}
for future in futures:
user_id = futures[future]
try:
result = future.result()
results[user_id] = result
print(f"user {user_id} handled successfully")
except Exception as e:
print(f"error handling user {user_id}: {e}")
results[user_id] = f"error: {e}"
return results
users = [1001, 1002, 1003, 1004, 1005, 1006]
results = process_users(users)
for user_id, result in results.items():
print(f"user {user_id}: {result}")شبیهسازی Idempotency در threading
سناریوی واقعی: یک سیستم پردازش تراکنش بانکی دارید. اگر یک تراکنش بهدلیل خطای شبکه با شکست مواجه شود، سیستم بهطور خودکار آن را دوباره اجرا میکند. اما اگر تراکنش قبلاً انجام شده باشد، نباید دوباره اجرا شود تا از پرداخت دوباره جلوگیری شود.
import threading
import time
import uuid
class TransactionProcessor:
def __init__(self):
self.lock = threading.Lock()
self.processed_transactions = set()
def process_payment(self, transaction_id, amount, idempotency_key):
with self.lock:
if idempotency_key in self.processed_transactions:
print(f"transaction {transaction_id} already processed (idempotency key: {idempotency_key})")
return "already_processed"
else:
print(f"processing transaction {transaction_id}...")
time.sleep(0.5)
if amount > 1000:
raise ValueError("amount exceeds daily limit")
self.processed_transactions.add(idempotency_key)
print(f"transaction {transaction_id} processed successfully")
return "success"
def process_with_retry(processor, transaction_id, amount, max_retries=3):
idempotency_key = f"{transaction_id}_{str(uuid.uuid4())[:8]}"
for attempt in range(max_retries):
try:
result = processor.process_payment(transaction_id, amount, idempotency_key)
return result
except Exception as e:
print(f"attempt {attempt + 1} for transaction {transaction_id} failed: {e}")
if attempt == max_retries - 1:
raise
time.sleep(0.5 * (2 ** attempt))
return "failed"
processor = TransactionProcessor()
transactions = [
{"id": "TXN-001", "amount": 500},
{"id": "TXN-002", "amount": 1500},
{"id": "TXN-003", "amount": 700}
]
for tx in transactions:
try:
result = process_with_retry(processor, tx["id"], tx["amount"], max_retries=3)
print(f"transaction {tx['id']}: {result}")
except Exception as e:
print(f"transaction {tx['id']} failed permanently: {e}")
print("\nretrying transaction TXN-002 with different idempotency key...")
try:
result = process_with_retry(processor, "TXN-002", 1500, max_retries=2)
print(f"retry result: {result}")
except Exception as e:
print(f"retry failed: {e}")مدیریت چندین ترد و جمعآوری نتایج
سناریوی واقعی: یک سیستم Crawler دارید که باید از ۱۰ سایت مختلف، اطلاعات قیمت محصولات را دریافت کند. هر سایت ممکن است کند پاسخ دهد یا با خطا مواجه شود. میخواهید در نهایت، نتایج موفق را جمعآوری کنید و خطاهای هر سایت را لاگ کنید.
from concurrent.futures import ThreadPoolExecutor, TimeoutError
import threading
import time
import queue
import random
class CrawlerManager:
def __init__(self, timeout_per_site=2.0, max_retries=2):
self.timeout = timeout_per_site
self.max_retries = max_retries
self.results = {}
self.errors = {}
self.lock = threading.Lock()
def crawl_site(self, site_name):
print(f"crawling {site_name}...")
delay = random.uniform(0.3, 3.0)
time.sleep(delay)
if random.random() < 0.3:
raise ConnectionError(f"cannot connect to {site_name}")
if random.random() < 0.2:
raise ValueError(f"invalid data from {site_name}")
return {site_name: "data_content"}
def crawl_with_timeout(self, site_name):
for attempt in range(self.max_retries):
try:
with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(self.crawl_site, site_name)
result = future.result(timeout=self.timeout)
with self.lock:
self.results[site_name] = result
return True
except TimeoutError:
print(f"timeout crawling {site_name}, attempt {attempt + 1}")
except Exception as e:
print(f"error crawling {site_name}, attempt {attempt + 1}: {e}")
if attempt < self.max_retries - 1:
time.sleep(0.5 * (2 ** attempt))
with self.lock:
self.errors[site_name] = "all retries failed"
return False
def crawl_all(self, sites, max_workers=5):
threads = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {
executor.submit(self.crawl_with_timeout, site): site
for site in sites
}
for future in futures:
site = futures[future]
try:
success = future.result()
if success:
print(f"{site} crawled successfully")
else:
print(f"{site} failed after retries")
except Exception as e:
print(f"unexpected error for {site}: {e}")
return self.results, self.errors
manager = CrawlerManager(timeout_per_site=2.0, max_retries=2)
sites = [
"site1.com", "site2.com", "site3.com",
"site4.com", "site5.com", "site6.com"
]
results, errors = manager.crawl_all(sites, max_workers=3)
print(f"\nsuccessful crawls ({len(results)}):")
for site, data in results.items():
print(f" {site}: {data}")
print(f"\nfailed crawls ({len(errors)}):")
for site, error in errors.items():
print(f" {site}: {error}")بهترین شیوهها (Best Practices) در مدیریت خطای threading
۱. همیشه از threading.Event برای سیگنالدهی استفاده کنید
بهجای تلاش برای قطع اجباری تردها، از Event برای هماهنگی استفاده کنید.
- از
concurrent.futuresبرای مدیریت آسانتر استفاده کنید این کتابخانه، مدیریت Timeout و استثناها را بسیار سادهتر میکند. - همیشه استثناهای تردها را مدیریت کنید یا با
threading.excepthook، یا با استفاده ازQueueیاFuture، مطمئن شوید که خطاهای تردها نادیده گرفته نمیشوند. - برای عملیاتهای طولانی، از چک کردن دورهای
Eventاستفاده کنید ترد شما باید بهگونهای طراحی شود که بهصورت دورهای وضعیتEventرا بررسی کند. - از Exponential Backoff با Jitter در Retry استفاده کنید این کار از هجوم به سرویسهای خارجی جلوگیری میکند.
- اطمینان از Idempotency قبل از استفاده از Retry اگر عملیات Idempotent نیست، برای Retry کردن باید از شناسههای یکتا (Idempotency Key) استفاده کنید.
- لاگهای دقیق نگه دارید در هر مرحله از Timeout و Retry، لاگ بنویسید تا بتوانید مشکلات را ردیابی کنید.
- از Thread Pool بهجای ایجاد تردهای جدید استفاده کنید
ThreadPoolExecutorمدیریت بهتری روی منابع دارد و خطاها را بهصورت یکپارچهتری مدیریت میکند.
جمعبندی
مدیریت خطا در threading نیازمند دقت و توجه ویژهای است. برخلاف asyncio که ابزارهای توکار قدرتمندی برای Timeout و Cancellation دارد، در threading باید از روشهای هماهنگی مثل Event و concurrent.futures استفاده کنیم.
الگوهای اصلی که در این فصل بررسی کردیم عبارتند از:
- Timeout: با استفاده از
threading.Timer،threading.Eventوconcurrent.futures - Retry: با تأخیر ثابت، تأخیر تصاعدی و شرطی
- ترکیب Timeout و Retry: برای ساخت برنامههای مقاوم در برابر خطا
- مدیریت استثناها: با
threading.excepthook،QueueوFuture - Idempotency: برای اطمینان از ایمنی Retry کردن
با رعایت این اصول و الگوها، میتوانید برنامههایی بنویسید که در برابر خطاهای موقت، طولانی شدن عملیاتها و شرایط غیرمنتظره، مقاوم باشند.