فهرست مقاله
فرض کنید یک رابطِ برنامه منتظرِ نتیجهای است و کاربر دیگر نمیخواهد منتظر بماند. سه پرسش جدا داریم: آیا نتیجه آماده است، آیا درخواستِ لغو پذیرفته میشود، و آیا کاری واقعاً متوقف شده است؟ نامِ Future این سه پرسش را به یک پاسخ تبدیل نمیکند. این درس، قراردادِ یک شیءِ نتیجه را با ابزارهایِ مخصوصِ آزمون بررسی میکند؛ هیچ کارگر، حسگر، فرمانِ بیرونی یا محاسبهٔ همزمانی راه نمیاندازد.
۱. Future وضعیتِ نتیجه را نگه میدارد، نه کلیدِ توقف را

موضوع، کلاسِ concurrent.futures.Future است. در استفادهٔ معمول، Executor آن را برای یک فراخوانی میسازد؛ اما مرجعِ رسمیِ Future ساختنِ مستقیم و تعیینِ وضعیت را برای آزمون مجاز میداند. از همین استثنایِ مستند استفاده میکنیم. مقدارِ۱۲۱۰ فقط یک عددِ صحیحِ ساختگی است، بدونِ واحدِ فیزیکی. علامتِ running در این مثال نیز مشاهدهٔ اجرایِ یک تابع یا اشغالِ پردازنده نیست.
شیءِ تازه نه لغوشده، نه درحالِ اجرا و نه پایانیافته است. دریافتِ نتیجه با مهلتِ صفر، TimeoutError دارد؛ چون هنوز نتیجهای تعیین نشده است. در مقابل، پس از تعیینِ مقدار، done درست و running نادرست میشود. done معنایِ «به یک پایان رسیده» دارد؛ لغوشدن و پایان با خطا نیز در همین مفهوم میگنجند. برای تشخیصِ موفقیت باید نتیجه یا خطایِ ثبتشده را هم مشاهده کنیم.
در آزمایشِ عمرِ shield و Task منتظر، Taskها و دروازههایِ asyncio واقعاً اجرا میشدند و مسیرِ انتقالِ لغو بررسی میشد. آن کلاسها با Future حاضر یکسان نیستند. اینجا coroutine، await و shield نداریم؛ حتی خطایِ دریافتِ نتیجهٔ معلق متفاوت است. نتیجهٔ آن آزمایش را به این رابط منتقل نمیکنیم و از این رابط هم سیاستِ لغوِ Task نمیسازیم.
۲. مرزِ شروع، پاسخِ cancel را عوض میکند

نخست، پیش از اعلامِ شروع cancel را میزنیم. پاسخ درست است و شیء لغوشده میماند. سپس متدِ مخصوصِ آزمون، set_running_or_notify_cancel، نادرست برمیگرداند: لغو قبلاً پذیرفته شده است. این پاسخ به پیادهسازِ Executor میگوید کارِ متناظر را آغاز نکند. در نمونهٔ ما اصلاً کارِ متناظری وجود ندارد؛ فقط گذارِ عمومیِ مستند و پرچمهایِ آن مشاهده میشوند.
در شاهدِ دوم ترتیب را عوض میکنیم. ابتدا همان متد درست برمیگرداند و وضعیت running میشود؛ cancel بعدی نادرست است. نتیجه هنوز آماده نیست و مشاهدهٔ آن با مهلتِ صفر TimeoutError دارد. سپس با setter مخصوصِ آزمون عدد را قرار میدهیم. نتیجه قابلِ دریافت میشود، بدونِ آنکه لغوِ ردشده آن را از بین برده باشد. نادرستبودنِ cancel، بهتنهایی رسیدِ تمامشدنِ کار نیست.
اعلامِ شروع فقط یکبار مجاز است و نباید پس از تعیینِ مقدار یا خطا فراخوانی شود. رابطِ محدودِ ما این موارد را پیش از فراخوانیِ دوباره، با برچسبِ own_start_rejected رد میکند. این سیاستِ خودمان است؛ نوعِ خطایِ فراخوانیِ نامعتبرِ تکراریِ کتابخانه را آزمایش نمیکنیم. در مقابل، ردشدنِ setter مقدار یا خطا روی شیءِ پایانیافته با InvalidStateError مستقیماً در مرجعِ جاری مستند است.
۳. مهلتِ صفر و callback را جدا مشاهده کنیم

result و exception هر دو میتوانند با timeout برابر صفر فراخوانی شوند. برای وضعیتِ معلق یا درحالِ اجرا، هر دو TimeoutError میدهند؛ پرچمِ لغو عوض نمیشود. پس تمامشدنِ همین مشاهده، درخواستِ لغو نیست. هیچ ساعت یا مدتِ سپریشدهای اندازه نمیگیریم. همچنین نامِ TimeoutError را به معنیِ شکستِ کارِ بیرونی نمیگیریم؛ در این آزمایش فقط نبودِ نتیجه در نقطهٔ مشاهده بیان میشود.
پس از تعیینِ مقدار، result همان مقدار و exception مقدارِ None میدهد. پس از تعیینِ ValueError ساختگی، result همان شیءِ خطا را پرتاب میکند و exception همان شیء را برمیگرداند. مقایسهٔ هویتِ خطا در کد جدا ثبت میشود. برای شیءِ لغوشده، هر دو دریافتکننده CancelledError دارند؛ پایانیافتنِ شیء مجوزِ خواندنِ یک مقدارِ موفقِ ساختگی نیست.
اعلانهایِ امنِ این درس فقط یک رکورد به فهرست اضافه میکنند. دو callback که پیش از پایان ثبت شدهاند، هنگامِ پایان به ترتیبِ ثبت فراخوانی میشوند و همان Future را دریافت میکنند. اگر callback را پس از پایان یا لغو ثبت کنیم، همانجا فراخوانی میشود. این مشاهده، ترتیبِ همهٔ رویدادهایِ برنامه یا نخِ اجرایِ دلخواهِ ما را تضمین نمیکند. خطایِ callback و رفتارِ تعریفنشدهٔ BaseException در دامنهٔ مثال نیست.
۴. کد کامل و جدولِ مستقلِ گذارها

کد را با نامِ future_cancel_state_boundary.py ذخیره و با پایتون اجرا کنید. وابستگیِ بیرونی ندارد؛ نسخهٔ اجرایِ این آزمایش CPython3.14.5 و نسخهٔ مستنداتِ خواندهشده 3.14.8 است. خروجی، نسخه و تمامِ چهارده گروهِ منطقی را ثبت میکند. JSON کامل از کانالِ بایتیِ خروجی، با UTF8 و پایانِ خطِ LF نوشته میشود تا نمایشِ خروجی با فایلِ ثبتشده یکسان بماند.
"""Bounded Future unit-test fixtures; no executor, job, sleep or elapsed timer."""
from concurrent.futures import CancelledError, Future, InvalidStateError
from itertools import product
import json
import platform
import sys
ACTIONS = ("cancel", "start", "value", "error", "callback", "peek")
def validate_case(actions, value):
if type(actions) is not tuple or len(actions) > 8:
raise ValueError("actions must be exact tuple of at most eight labels")
if any(type(x) is not str or x not in ACTIONS for x in actions):
raise ValueError("each action must be an exact declared string")
if type(value) is not int or not -10000 <= value <= 10000:
raise ValueError("value must be exact integer in [-10000,10000]")
def observation(future, authored_error):
try:
value = future.result(timeout=0)
result = {"kind": "value", "value": value}
except CancelledError:
result = {"kind": "CancelledError"}
except TimeoutError:
result = {"kind": "TimeoutError"}
except ValueError as error:
result = {"kind": "ValueError", "same_authored_error": error is authored_error}
try:
error = future.exception(timeout=0)
exception = {"kind": "None"} if error is None else {
"kind": "ValueError", "same_authored_error": error is authored_error}
except CancelledError:
exception = {"kind": "CancelledError"}
except TimeoutError:
exception = {"kind": "TimeoutError"}
return {"cancelled": future.cancelled(), "running": future.running(),
"done": future.done(), "result": result, "exception": exception}
def observe(actions, value=1210):
"""Use setters only as documented unit-test APIs, never as a worker."""
validate_case(actions, value)
future = Future()
authored_error = ValueError("synthetic result failure")
callbacks, trace = [], []
callback_number = 0
start_called = False
initial = observation(future, authored_error)
for action in actions:
if action == "cancel":
effect = {"kind": "cancel", "accepted": future.cancel()}
elif action == "start":
# Own admission policy avoids the documented illegal repeated setter.
if start_called or (future.done() and not future.cancelled()):
effect = {"kind": "own_start_rejected"}
else:
start_called = True
effect = {"kind": "start", "accepted": future.set_running_or_notify_cancel()}
elif action in ("value", "error"):
try:
if action == "value":
future.set_result(value)
else:
future.set_exception(authored_error)
effect = {"kind": "set_" + action}
except InvalidStateError:
effect = {"kind": "InvalidStateError"}
elif action == "callback":
tag = callback_number
callback_number += 1
def notified(done, tag=tag):
callbacks.append({"tag": tag, "same_future": done is future,
"done": done.done(), "cancelled": done.cancelled()})
future.add_done_callback(notified)
effect = {"kind": "callback_registered", "tag": tag}
else:
effect = {"kind": "peek"}
trace.append({"action": action, "effect": effect,
"observation": observation(future, authored_error),
"callback_events": [dict(x) for x in callbacks]})
return {"initial": initial, "trace": trace}
def table_reference(actions, value):
"""Independent literal state table; no Future, main helper or validator."""
state = "pending"
start_used = False
registered = 0
waiting_callbacks, events, trace = [], [], []
flags = {"pending": (False, False, False), "running": (False, True, False),
"cancelled": (True, False, True), "value": (False, False, True),
"error": (False, False, True)}
outcomes = {"pending": ({"kind": "TimeoutError"}, {"kind": "TimeoutError"}),
"running": ({"kind": "TimeoutError"}, {"kind": "TimeoutError"}),
"cancelled": ({"kind": "CancelledError"}, {"kind": "CancelledError"}),
"value": ({"kind": "value", "value": value}, {"kind": "None"}),
"error": ({"kind": "ValueError", "same_authored_error": True},
{"kind": "ValueError", "same_authored_error": True})}
def snap():
cancelled, running, done = flags[state]
result, exception = outcomes[state]
return dict(cancelled=cancelled, running=running, done=done,
result=dict(result), exception=dict(exception))
def notify(tags):
for tag in tags:
events.append(dict(tag=tag, same_future=True, done=True,
cancelled=state == "cancelled"))
initial = snap()
for action in actions:
was_terminal = state in ("cancelled", "value", "error")
if action == "cancel":
accepted = state in ("pending", "cancelled")
if accepted:
state = "cancelled"
effect = dict(kind="cancel", accepted=accepted)
elif action == "start":
if start_used or state in ("value", "error"):
effect = dict(kind="own_start_rejected")
else:
start_used = True
accepted = state == "pending"
if accepted:
state = "running"
effect = dict(kind="start", accepted=accepted)
elif action in ("value", "error"):
if state in ("pending", "running"):
state = action
effect = dict(kind="set_" + action)
else:
effect = dict(kind="InvalidStateError")
elif action == "callback":
tag = registered
registered += 1
if was_terminal:
notify([tag])
else:
waiting_callbacks.append(tag)
effect = dict(kind="callback_registered", tag=tag)
else:
effect = dict(kind="peek")
if not was_terminal and state in ("cancelled", "value", "error"):
notify(waiting_callbacks)
waiting_callbacks = []
trace.append(dict(action=action, effect=effect, observation=snap(),
callback_events=[dict(x) for x in events]))
return dict(initial=initial, trace=trace)
def rejects(function, *args):
try:
function(*args)
except ValueError:
return True
return False
def run():
checks = {}
cancelled = observe(("callback", "cancel", "start", "peek"))
running = observe(("start", "cancel", "peek", "value"))
checks["pending_cancel_denies_later_testing_start"] = (
cancelled["trace"][1]["effect"] == dict(kind="cancel", accepted=True)
and cancelled["trace"][2]["effect"] == dict(kind="start", accepted=False)
and cancelled["trace"][-1]["observation"]["cancelled"] is True)
checks["marked_running_rejects_cancel_without_executing_a_job"] = (
running["trace"][0]["effect"] == dict(kind="start", accepted=True)
and running["trace"][1]["effect"] == dict(kind="cancel", accepted=False)
and running["trace"][1]["observation"]["running"] is True)
pending = observe(("peek",))
checks["pending_result_timeout_zero_does_not_cancel_or_finish"] = (
pending["initial"] == pending["trace"][0]["observation"]
and pending["initial"]["result"] == dict(kind="TimeoutError")
and not any(pending["initial"][key] for key in ("cancelled", "running", "done")))
checks["running_result_timeout_zero_keeps_running"] = (
running["trace"][2]["observation"]["result"] == dict(kind="TimeoutError")
and running["trace"][2]["observation"]["running"] is True
and running["trace"][2]["observation"]["done"] is False)
checks["exception_timeout_zero_is_not_a_cancellation_request"] = (
pending["initial"]["exception"] == dict(kind="TimeoutError")
and running["trace"][2]["observation"]["exception"] == dict(kind="TimeoutError")
and running["trace"][2]["observation"]["cancelled"] is False)
values = [observe(prefix + ("value", "cancel"), -42) for prefix in ((), ("start",))]
checks["value_completion_preserves_value_and_exception_none"] = all(
x["trace"][-1]["effect"] == dict(kind="cancel", accepted=False)
and x["trace"][-1]["observation"] == dict(cancelled=False, running=False, done=True,
result=dict(kind="value", value=-42), exception=dict(kind="None")) for x in values)
error = observe(("start", "error", "peek"))
checks["error_result_and_exception_keep_same_authored_error_identity"] = (
error["trace"][-1]["observation"] == dict(cancelled=False, running=False, done=True,
result=dict(kind="ValueError", same_authored_error=True),
exception=dict(kind="ValueError", same_authored_error=True)))
checks["both_cancelled_accessors_raise_cancelled_error"] = all(
cancelled["trace"][-1]["observation"][key] == dict(kind="CancelledError")
for key in ("result", "exception"))
callbacks = observe(("callback", "callback", "start", "value"))
checks["precompletion_safe_callbacks_receive_same_future_in_registration_order"] = (
callbacks["trace"][2]["callback_events"] == []
and callbacks["trace"][3]["callback_events"] == [
dict(tag=i, same_future=True, done=True, cancelled=False) for i in range(2)])
checks["postterminal_safe_callback_is_immediate_for_all_three_outcomes"] = all(
observe((terminal, "callback"))["trace"][-1]["callback_events"] == [
dict(tag=0, same_future=True, done=True, cancelled=terminal == "cancel")]
for terminal in ("cancel", "value", "error"))
checks["terminal_setters_raise_invalid_state_without_replacing_outcome"] = all(
all(x["effect"] == dict(kind="InvalidStateError") for x in result["trace"][1:3])
and result["trace"][0]["observation"] == result["trace"][-1]["observation"]
for result in (observe((terminal, "value", "error", "peek"))
for terminal in ("cancel", "value", "error")))
class TupleChild(tuple): pass
class StringChild(str): pass
class IntChild(int): pass
pulled = []
def lazy():
pulled.append(True)
yield "peek"
invalid = [(None, 0), ([], 0), (TupleChild(()), 0), (lazy(), 0),
(("peek",) * 9, 0), (("unknown",), 0), ((True,), 0),
((StringChild("peek"),), 0), (("peek", "invalid"), 0),
((), True), ((), 1.0), ((), IntChild(1)), ((), -10001), ((), 10001)]
global Future
original_factory = Future
calls = []
def forbidden_factory():
calls.append(True)
raise AssertionError("invalid case constructed a Future")
try:
Future = forbidden_factory
invalid_ok = all(rejects(observe, actions, value) for actions, value in invalid)
finally:
Future = original_factory
checks["full_batch_validation_caps_before_factory_and_generator_consumption_ONE_group"] = (
invalid_ok and len(invalid) == 14 and calls == [] and pulled == [])
profiles = cases = observations = 0
for length in range(5):
for actions in product(ACTIONS, repeat=length):
profiles += 1
for value in (-1, 0, 1210):
actual = observe(actions, value)
assert actual == table_reference(actions, value), (actions, value)
cases += 1
observations += 1 + len(actions)
checks["independent_literal_transition_table_all_bounded_schedules_ONE_group"] = (
profiles == 1555 and cases == 4665 and observations == 22395)
actions = ("callback", "start", "peek", "value", "callback", "cancel", "start", "peek")
first = observe(actions, 10000)
snapshot = json.loads(json.dumps(first))
second = observe(actions, 10000)
second["trace"][0]["effect"]["tag"] = -1
second["trace"][-1]["callback_events"].clear()
checks["fresh_observation_maps_and_maxeight_action_integer_endpoints"] = (
first == snapshot and actions[-1] == "peek" and len(actions) == 8
and observe(("value",), -10000)["trace"][0]["observation"]["result"]["value"] == -10000
and first["trace"][-1]["observation"]["result"]["value"] == 10000
and first["trace"][-2]["effect"] == dict(kind="own_start_rejected"))
assert len(checks) == 14 and all(type(v) is bool and v for v in checks.values()), checks
return {"python": platform.python_version(), "implementation": platform.python_implementation(),
"dependencies": "standard library only", "manual_cancel_before_start": cancelled,
"manual_marked_running_then_cancel_then_value": running,
"finite_family_in_ONE_group": dict(action_profiles=profiles, value_cases=cases,
observations=observations, max_family_actions=4),
"invalid_cases_in_ONE_group": len(invalid),
"limits": dict(max_actions=8, exact_action_labels=ACTIONS, result_integer_min=-10000,
result_integer_max=10000),
"units": "Dimensionless synthetic result integers and action/state counts; timeout=0 is a nonwaiting observation, not measured elapsed time.",
"checks": checks,
"scope": "Future created and setters called only as documented unit-test fixtures. No Executor, callable job, asyncio Task, thread, process, network, sleep, hardware, throughput or cancellation-latency experiment. Own repeated-start rejection is not a claim about undocumented repeated stdlib calls. Safe callbacks only; no callback failures or concurrent scheduling tested."}
if __name__ == "__main__":
sys.stdout.buffer.write((json.dumps(run(), ensure_ascii=True, indent=2) + "\n").encode("utf-8"))
ورودیِ عمومیِ مثال tuple دقیق با حداکثر هشت عمل است. هر عضو باید یکی از شش رشتهٔ دقیقِ اعلامشده باشد؛ زیرنوعِ رشته، فهرست و مولد پذیرفته نمیشوند. مقدار نیز int دقیق در بازهٔ منفی دههزار تا دههزار است؛ bool و اعشار رد میشوند. تمامِ دسته پیش از ساختِ Future بررسی میشود؛ خطایِ عملِ آخر نباید بخشی از دسته را اجرا کرده باشد. مولدِ ردشده حتی مصرف هم نمیشود.
مرجعِ مستقل، جدولِ پنج وضعیتِ معلق، درحالِ اجرا، لغوشده، مقدار و خطاست. این جدول Future، تابعِ اصلی، اعتبارسنج یا جزئیاتِ خصوصیِ کتابخانه را صدا نمیزند. فهرستِ اعلانهایِ منتظر و مشاهدهٔ هر وضعیت را جدا میسازد. همهٔ رشتههایِ عمل تا طولِ چهار با سه مقدار مقایسه میشوند:۱۵۵۵ الگو و۴۶۶۵ حالت، در یک گروهِ بررسی؛ نه هزاران گروهِ مستقل و نه پوششِ زمانبندیِ واقعیِ کارگرها.
{
"python": "3.14.5",
"implementation": "CPython",
"dependencies": "standard library only",
"manual_cancel_before_start": {
"initial": {
"cancelled": false,
"running": false,
"done": false,
"result": {
"kind": "TimeoutError"
},
"exception": {
"kind": "TimeoutError"
}
},
"trace": [
{
"action": "callback",
"effect": {
"kind": "callback_registered",
"tag": 0
},
"observation": {
"cancelled": false,
"running": false,
"done": false,
"result": {
"kind": "TimeoutError"
},
"exception": {
"kind": "TimeoutError"
}
},
"callback_events": []
},
{
"action": "cancel",
"effect": {
"kind": "cancel",
"accepted": true
},
"observation": {
"cancelled": true,
"running": false,
"done": true,
"result": {
"kind": "CancelledError"
},
"exception": {
"kind": "CancelledError"
}
},
"callback_events": [
{
"tag": 0,
"same_future": true,
"done": true,
"cancelled": true
}
]
},
{
"action": "start",
"effect": {
"kind": "start",
"accepted": false
},
"observation": {
"cancelled": true,
"running": false,
"done": true,
"result": {
"kind": "CancelledError"
},
"exception": {
"kind": "CancelledError"
}
},
"callback_events": [
{
"tag": 0,
"same_future": true,
"done": true,
"cancelled": true
}
]
},
{
"action": "peek",
"effect": {
"kind": "peek"
},
"observation": {
"cancelled": true,
"running": false,
"done": true,
"result": {
"kind": "CancelledError"
},
"exception": {
"kind": "CancelledError"
}
},
"callback_events": [
{
"tag": 0,
"same_future": true,
"done": true,
"cancelled": true
}
]
}
]
},
"manual_marked_running_then_cancel_then_value": {
"initial": {
"cancelled": false,
"running": false,
"done": false,
"result": {
"kind": "TimeoutError"
},
"exception": {
"kind": "TimeoutError"
}
},
"trace": [
{
"action": "start",
"effect": {
"kind": "start",
"accepted": true
},
"observation": {
"cancelled": false,
"running": true,
"done": false,
"result": {
"kind": "TimeoutError"
},
"exception": {
"kind": "TimeoutError"
}
},
"callback_events": []
},
{
"action": "cancel",
"effect": {
"kind": "cancel",
"accepted": false
},
"observation": {
"cancelled": false,
"running": true,
"done": false,
"result": {
"kind": "TimeoutError"
},
"exception": {
"kind": "TimeoutError"
}
},
"callback_events": []
},
{
"action": "peek",
"effect": {
"kind": "peek"
},
"observation": {
"cancelled": false,
"running": true,
"done": false,
"result": {
"kind": "TimeoutError"
},
"exception": {
"kind": "TimeoutError"
}
},
"callback_events": []
},
{
"action": "value",
"effect": {
"kind": "set_value"
},
"observation": {
"cancelled": false,
"running": false,
"done": true,
"result": {
"kind": "value",
"value": 1210
},
"exception": {
"kind": "None"
}
},
"callback_events": []
}
]
},
"finite_family_in_ONE_group": {
"action_profiles": 1555,
"value_cases": 4665,
"observations": 22395,
"max_family_actions": 4
},
"invalid_cases_in_ONE_group": 14,
"limits": {
"max_actions": 8,
"exact_action_labels": [
"cancel",
"start",
"value",
"error",
"callback",
"peek"
],
"result_integer_min": -10000,
"result_integer_max": 10000
},
"units": "Dimensionless synthetic result integers and action/state counts; timeout=0 is a nonwaiting observation, not measured elapsed time.",
"checks": {
"pending_cancel_denies_later_testing_start": true,
"marked_running_rejects_cancel_without_executing_a_job": true,
"pending_result_timeout_zero_does_not_cancel_or_finish": true,
"running_result_timeout_zero_keeps_running": true,
"exception_timeout_zero_is_not_a_cancellation_request": true,
"value_completion_preserves_value_and_exception_none": true,
"error_result_and_exception_keep_same_authored_error_identity": true,
"both_cancelled_accessors_raise_cancelled_error": true,
"precompletion_safe_callbacks_receive_same_future_in_registration_order": true,
"postterminal_safe_callback_is_immediate_for_all_three_outcomes": true,
"terminal_setters_raise_invalid_state_without_replacing_outcome": true,
"full_batch_validation_caps_before_factory_and_generator_consumption_ONE_group": true,
"independent_literal_transition_table_all_bounded_schedules_ONE_group": true,
"fresh_observation_maps_and_maxeight_action_integer_endpoints": true
},
"scope": "Future created and setters called only as documented unit-test fixtures. No Executor, callable job, asyncio Task, thread, process, network, sleep, hardware, throughput or cancellation-latency experiment. Own repeated-start rejection is not a claim about undocumented repeated stdlib calls. Safe callbacks only; no callback failures or concurrent scheduling tested."
}
سقفِ عمومیِ هشت عمل جداگانه شاهد دارد؛ خانوادهٔ مرجع تا طولِ چهار محدود است. بررسیهایِ دیگر، مرزِ عدد، خطایِ setter پایانیافته، ترتیبِ اعلان، هویتِ خطا و مستقلبودنِ نقشههایِ خروجی را میسنجند. طرحِ تاریخیِ PEP3148 علتِ وجودِ گذارهایِ مخصوصِ آزمون را روشن میکند؛ برای خطاهایِ افزودهشده در نسخههایِ بعد، مستنداتِ جاری ملاک است. تاریخِ ایجادِ آن طرح، تاریخِ انتشارِ این آموزش یا نسخهٔ اجرایِ کد نیست.
۵. چهار پرسش دربارهٔ لغو و دریافتِ نتیجه

پرسش اول: اگر cancel نادرست باشد، میتوان نتیجه را فوراً موفق فرض کرد؟ پاسخ: خیر؛ شاهدِ running هنوز نتیجه ندارد و هر دو مشاهدهٔ صفرمهلت خطا میدهند. نادرستبودنِ لغو، دربارهٔ پذیرشِ درخواست است. برای موفقیت، نتیجهٔ پایانیافته را جدا میخوانیم و خطایِ کار یا لغو را با مقدار اشتباه نمیگیریم.
پرسش دوم: آیا TimeoutError باعث میشود تعیینِ نتیجهٔ بعدی نامعتبر باشد؟ پاسخ: در این مثال خیر؛ مشاهده وضعیت را پایان نمیدهد. پس از مشاهدهٔ شیءِ درحالِ اجرا، setter مخصوصِ آزمون عدد۱۲۱۰ را قرار میدهد و همان عدد قابلِ دریافت است. این ترتیب، تضمینِ مهلت یا تکمیلِ یک عملیاتِ واقعی پس از ترکِ کاربر نیست؛ عملیاتِ واقعی در کد نداریم.
پرسش سوم: چرا done برای Future لغوشده درست است ولی result مقدار نمیدهد؟ پاسخ: لغو یکی از پایانهاست و دریافتِ آن CancelledError دارد. برای نمونهای دیگر از جداییِ پرچم و مسئولیت، قراردادِ تحویل و اعلامِ پایانِ graphlib را ببینید؛ آن درس دربارهٔ پیشنیازهاست. اشتراکِ واژهٔ پایان، این دو قرارداد را یکی نمیکند.
پرسش چهارم: آیا باید در برنامهٔ عادی خودمان set_running_or_notify_cancel را صدا بزنیم؟ پاسخ: این درس چنین توصیهای ندارد؛ متد برای پیادهسازِ Executor و آزمون مستند شده است. مثال را به یک کلیدِ توقفِ نخ تبدیل نکنید. برای کارِ واقعی، مالکیتِ منابع، همکاریِ تابع با لغو و دریافتِ نتیجه باید جدا طراحی شوند؛ مشاهدهٔ همین جدول، آن مسئولیتها را انجام نمیدهد.
منابع و مطالعهٔ بیشتر
بررسی منابع: ۲۹ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

