Docsの目次

並行処理の結果を一度受け取る

Task結果handleとSupervisor monitorのS2移行はNagi 0.1.11で公開済みです。利用するcompilerが0.1.11以降であることを確認してください。旧spawn work()は引き続き使えます。実行可能なHigh・手書きLowの例があります。

from std.task import discard

async def answer() -> i64:
    return 42

async def main() -> Result[unit, Error]:
    async with scope:
        task = spawn answer()
        result = await task
        match result:
            case Ok(value):
                print(value)
            case Err(failure):
                print("子taskが故障しました")
        background = spawn sleep(10)
        discard(background)
    return ok(print("完了"))

task = spawn answer()は並行処理を開始し、Task[i64]を得ます。await taskはhandleを一度消費し、子の実際の終了を待ってからResult[i64, TaskFailure]を返します。通常のasync呼出しを直接awaitする書き方も使えます。Futureそのものの保存には対応しません。

Taskは結果の型にかかわらず非Copy・非Clone・非sharedです。作成したscope内のローカルに限り、正常なbinding出口・scope出口・loop継続までにawaitかdiscardが必要です。両方の分岐で義務を満たしてください。from std.ownership import moveのalias = move(task)はhandleと義務を移します。裸のmove(task)で放棄はできません。

awaitもdiscardもしないままscopeを出ると、正常終了経路に未処理のTaskが残るためcheckが拒否します。

async def answer() -> i64:
    return 42

async def main() -> Result[unit, Error]:
    async with scope:
        task = spawn answer()
    return ok(print("done"))

値を使うならresult = await taskにして返却値を処理します。結果が不要ならfrom std.task import discardしてdiscard(task)を呼びます。どちらも子の終了をscopeが待つ点は同じです。

短絡and/orの右辺は評価が省略されることがあり、値が存在する場合のenv fallbackは評価されません。その中だけでawait/discardしても、正常出口の義務を満たしません。checkerは条件の定数値でこの義務を省略しないため、先にTaskを受け取り、得た通常のResultを条件付きで使ってください。左辺やenvのkeyのように必ず評価する位置での受取は有効です。

Taskを関数の引数や戻り値、field、List、Option、Resultなどのwrapper、他taskへ渡すことはできません。内側の別scopeで外側のTaskを受け取ることもできません。内側のscopeが終わってから、元のscopeで受け取ってください。scope内のreturnは引き続き未対応です。

このページの目次

業務結果とtask故障

子がResult[T, E]を返すと、受取型はResult[Result[T, E], TaskFailure]です。内側の業務Errだけでは兄弟を停止しません。panic、予期しない取消、同じscope内の旧spawnのErr、bridgeのprotocol故障は外側の故障になります。

外側と内側を一つのErrとして扱うと、Taskの故障と業務上の拒否を混同します。業務Errも取り出すには、外側のOkで内側のResultをもう一度matchします。

async def save() -> Result[i64, Error]:
    return error("rejected")

async def main() -> Result[unit, Error]:
    async with scope:
        task = spawn save()
        received = await task
        match received:
            case Ok(operation_result):
                match operation_result:
                    case Ok(value):
                        print(value)
                    case Err(problem):
                        print("business operation rejected")
            case Err(failure):
                print("task failed")
    return ok(print("done"))

この例は内側のErrを処理してdoneまで進みます。外側のErr(TaskFailure)を受け取った場合は、scope故障も残り、scope出口でErrになります。

from std.task import kind, message, TaskFailureKindで故障を調べられます。kind(failure)はCopyなenumで、定数はPanicked、Cancelled、LegacyError、Internalです。message(failure)はfailureを借用元とするview[str]です。借用が残る間、failureをmoveできません。TaskFailureはopaqueな非Copy・非Clone・非shared値で、Errorへの暗黙変換はありません。panic payloadの任意の文字列を保持する保証もありません。

故障を受け取りmatchしても、scopeの故障状態は残ります。最初に観測した故障をprimaryとし、兄弟へ取消を要求し、全子を実joinしてからscope出口のErrorを返します。観測順はspawn順とは限りません。bodyのtryによる元Errは、後から子が故障しても置換しません。

discard(task)はunitを返し、結果の受取を放棄します。子の停止、detach、故障の抑制、終了待ちの省略、資源closeの完了を意味しません。scopeはその子の終了も待ちます。Taskを含むscope内に旧spawn work()が混在する場合、その旧spawnのResult[unit, Error] Errは引き続きscope故障です。Supervisor/HTTPの旧連携も維持しています。

SupervisorとHTTPの移行

サービス例ではmonitorだけをTaskへ移し、HTTPの旧spawnを維持します。await monitor_taskのOk(inner)を親のtry innerで処理すると、Supervisorのterminal Errがbody Errとなり、HTTPへ取消を要求して直接の子の実join後に元Errorを返します。単にdiscardすると、この内側ErrはHTTPを止めません。正常なSupervisor shutdownはHTTPを継続します。HTTP故障が先に観測されれば、それがscopeのprimaryになります。

HTTP stateはSupervisor contextを保持しません。外部のshared/native Arcがcontextを保持すると、その解放までSupervisorのcleanupが待つ場合があり、Taskは参照循環を解消しません。

親FutureのDropやpanicでは取消を要求しますが、同期Dropから実join完了は保証できません。scopeの直接の子が終了しても、Rust連携内の任意の子taskやHTTP handlerの非同期close完了を保証するものではありません。yieldしない処理の強制停止や、DBなどの外部副作用の巻戻しも保証しません。asyncとscope、並行処理、実装と検証状況も参照してください。

Nagi 0.2.0 · Docsこのページのソース