したがって、Deferredは遅延した結果であり、しばらくすると実行結果が判明します。 Deferredに格納される結果は、非同期操作の実行中に発生した任意の値(実行の成功)またはエラー(例外)の場合があります。 操作の結果に関心があり、非同期関数Deferredから受け取ったので、実行の結果が判明した時点でアクションを実行したいと思います。 したがって、Deferredは、結果に加えて、結果ハンドラー(コールバック)とエラーハンドラー(エラーバック)のハンドラーのチェーンも格納します。
ハンドラーのチェーンをさらに詳しく考えてみましょう。
ハンドラーは「レイヤー」またはレベルに配置され、実行は上から下のレベルで明確に行われます。 同時に、コールバックハンドラーとerrbackハンドラーは各レベルにあり、要素の1つが存在しない場合があります。 各レベルで、コールバックまたはエラーバックのいずれかを実行できますが、両方は実行できません。 ハンドラーの実行は1回のみで、再入力はできません。
コールバックハンドラー関数は、1つの引数を持つ関数です-実行結果:
def callback(result): …
errbackハンドラー関数は、 Failureクラスにラップされた例外を引数として受け入れます。
def errback(failure): …
遅延実行は、Deferredに表示される結果から開始されます:成功した実行または例外。 結果に応じて、対応するハンドラーのブランチ(コールバックまたはエラーバック)が選択されます。 その後、対応するハンドラーが存在する次のレベルのハンドラーが検索されます。 図の例では、正常な実行結果が取得され、結果が
callback1
ハンドラーに渡されました。
さらに実行すると、下位レベルのハンドラーが呼び出されます。 コールバックまたはエラーバックが失敗ではない戻り値で終了した場合、実行は成功したと見なされ、次のレベルのコールバックハンドラによって入力に結果が送信されます。 ハンドラーの実行中に例外がスローされたか、Failure値が返された場合、errbackは次のレベルのコントロールに転送され、パラメーターとして例外を受け取ります。
この例では、
callback1
ハンドラーが成功し、その結果が
callback2
ハンドラーに渡され、そこで例外がスローされ、エラー
callback2
ハンドラーのチェーンへの移行に至りました。第3レベルで、errbackハンドラーは存在せず、例外を処理した
errback4
に渡され、成功を返しました実行の結果。これは現在Deferredの結果ですが、ハンドラはこれ以上ありません。 別のレベルのハンドラーがDeferredに追加された場合、それらはこの結果にアクセスできます。
他のすべてのPythonオブジェクトと同様に、Deferredオブジェクトは、他のオブジェクトからの参照がある限り存続します。 通常、Deferredを返したオブジェクトは保存します。 非同期操作の最後に結果をDeferredに転送する必要があります。 多くの場合、他の参加者(イベントハンドラーを追加)はDeferredへの参照を保存しないため、ハンドラーチェーンの最後でDeferredオブジェクトが破棄されます。 未処理の例外が残っている(実行が例外で終了し、ハンドラーがなくなった)遅延破壊が発生すると、デバッグメッセージがトレースバック例外とともに出力されます。 この状況は、通常の同期プログラムの最上位への未処理の例外の「ポップ」に似ています。
遅延二乗
コールバックとerrbackの戻り値は、別のDeferredにすることもできます。その場合、現在のDeferredのハンドラーチェーンの実行は、ネストされたDeferredのハンドラーチェーンの終わりまで中断されます。
図に示す例では、callback2ハンドラーは通常の結果ではなく、別のDeferred-Deferred2を返します。 この場合、現在のDeferredの実行は、Deferred2の実行結果が受信されるまで中断されます。 Deferred2の結果-成功または例外-は、最初のDeferredハンドラーの次のレベルに渡される結果になります。 この例では、Deferred2は例外で終了しました。この例外は、入力として最初のDeferredの
errback2
ハンドラーに渡されます。
エラーバック例外処理
各errback例外ハンドラーはtry..exceptブロックに類似しており、exceptブロックは通常、何らかのタイプの例外に応答します。この動作はFailureを使用して非常に簡単に再現できます。
def errback(failure): """ @param failure: (), failure @type failure: C{Failure} """ failure.trap(KeyError) print "Got key error: %r" % failure.value return 0
Failure
クラスの
trap
メソッドは、ラップされた例外が後続であるか、直接
KeyError
クラスであるかをチェックします。 そうでない場合は、元の例外が再びスローされ、現在のエラーバックの実行が中断されます。これにより、ハンドラチェーン内の次のエラーバックに制御が移され、例外タイプの不一致の場合の例外ブロックの動作が模倣されます(制御は次のブロックに移されます)。
value
プロパティは、エラーに関する追加情報を取得するために使用できる元の例外を格納します。
errbackハンドラーは次の2つの方法のいずれかで完了する必要があることに注意してください。
- 次のコールバックの入力値となる値を返します。つまり、例外が処理されたことを意味します。
- 元の例外または新しい例外をスローします-例外は処理されなかったか、新しい例外がスローされ、エラーバックチェーンは続行します。
3番目のオプションがあります-Deferredを返すと、ハンドラーのさらなる実行はDeferredの結果に依存します。
この例では、例外を処理し、結果として0を渡しました(たとえば、キーが存在しないことはそのゼロ値と同等です)。
事前に非同期の準備をする
非同期が表示されると、つまり、即時値ではなく一部の関数がDeferredを返すとすぐに、非同期は上記の関数のツリー全体に広がり始め、以前に同期していた関数からDeferredを強制的に返します(結果を直接返します)。 そのような変換の条件付きの例を考えてみましょう。
def f(): return 33 def g(): return f()*2
何らかの理由で関数
f
がすぐに結果を返せない場合、Deferredを返し始めます。
def f(): return Deferred().callback(33)
しかし、関数
g
Deferredを返すように強制され、ハンドラーのチェーンをキャッチします。
def g(): return f().addCallback(lambda result: result*2)
実際の関数でも同様の「変換」スキームが発生します。ツリーの関数呼び出しから遅延結果を取得し、コールバックハンドラーを関数の古い同期コードに対応するDeferredにアタッチします。例外ハンドラーがある場合は、追加し、 errbackハンドラー。
実際には、同期コードを非同期に再構築するよりも、非同期でDeferredを使用するコード部分を最初に特定する方が適切です。 非同期コードは、結果を直接構築できない呼び出しで始まります。
- ネットワーク入出力;
- DBMSネットワークサービス、memcachedへのアクセス。
- リモートRPC呼び出し。
- Workerモデルなどのスレッドで実行が強調表示される操作
アプリケーションの作成プロセスでは、この時点で非同期アクセスが存在することはよくありますが、まだ存在していません(たとえば、DBMSとのインターフェイスは実装されていません)。 この状況では、
defer.success
または
defer.fail
を使用して、すでに結果が含まれているDeferredを作成できます。 以下は、関数
f
を書き換える最短の方法です。
from twisted.internet import defer def f(): return defer.success(33)
呼び出された関数が結果を同期的に返すかDeferredを返すかがわからず、その動作に依存したくない場合は、呼び出しを
defer.maybeDeferred
でラップして、Deferredの呼び出しと同等のオプションを作成できます。
from twisted.internet import defer def g(): return defer.maybeDeferred(f).addCallback(lambda result: result*2)
このバージョンの
g
は、同期
f
と非同期
f
両方で機能します。
結論の代わりに
Deferredについては非常に長い間話すことができます。追加の読み物として、 前回の記事の最後にある資料のリストを再度お勧めします。