パート19: 欲しいと思っていても考えを改めると

はじめに

Twisted は現在進行形のプロジェクトですから、Twisted の開発者は日常的に新しい機能を追加したり、古い機能を拡張しています。 Twisted 10.1.0 のリリースでは、 Deferred クラスに新しい機能が追加されました。 cancellation です。 今日はこれについて掘り進めていきましょう。

非同期プログラミングはリクエストとレスポンスを分離し、新しい可能性をもたらします。 結果を要求することと、その結果を受け取ることの間に、それ以上は必要ないと決めるかもしれませんね。 “パート14: Deferred が無かったら” で作成した詩のプロキシサーバを考えてみましょう。 プロキシの動作は以下のようになります。少なくとも詩への最初のリクエストでは。

  • 詩へのリクエストがやってきます。

  • プロキシは詩を受け取るために実際のサーバに問い合わせます。

  • 詩を受け取り終えると、元のクライアントに送ります。

どれもがうまくいけば良いですが、詩を受け取る前にクライアントがハングしたらどうしましょうか? Paradise Lost の完全なテキストを要求したものの、実は Kojo の俳句を欲しかったとしたらどうでしょう。 プロキシは最初の詩をダウンロードすることに固執し、ゆっくりしたサーバはしばらく時間をかけてきます。 接続を閉じてしまい、ゆっくりしたサーバにはスリープしてもらった方が良いかもしれませんね。

図15:同期コードと例外”を思い出してください。同期プログラムでの概念的な制御フローを示すダイアグラムです。 あの図では、関数呼び出しが下に下っていき、例外は上に戻ってくることを確認しました。 同期する関数呼び出しをキャンセルしたいとき (これは単なる仮説です) は、 フロー制御は関数呼び出しと同じ方向に進んでいくでしょう。 図38の高レベルのコードから低レベルのコードに進んでいくようにです。

_images/p19_sync-cancel.png

図38:理論的なキャンセルもある、同期プログラムにおける情報の伝わり方

もちろん同期プログラムでは、キャンセルすべき箇所がないために、高レベルのコードは低レベルの操作が終了するまで再開さえできませんから、そのようなことは不可能です。 しかし非同期プログラムでは、高レベルのコードは低レベルのコードが実行終了する前にプログラムの制御を獲得します。 少なくとも、低レベルのリクエストが終了する前に、それをキャンセルできる可能性があります。

Twisted プログラムでは、低レベルのリクエストは Deferred オブジェクトによってひつとにまとめられ、未解決の非同期操作に対する「ハンドル」(“handle”) として考えられます。 遅延オブジェクト内の情報の通常のフローは下方向に向かいます、低レベルのコードから高レベルのコードに移ることです。 これは、同期プログラムで情報が戻ってくるフローに合致します。 Twisted 10.1.0 からは、高レベルのコードは情報を送り返すことができるようになりました。 低レベルのコードに、結果はもういらない、と伝えられるのです。 図39を見てください。

_images/p19_deferred-cancel.png

図39:キャンセルも含んだ遅延オブジェクトにおける情報の伝わり方

遅延オブジェクトをキャンセルする

遅延オブジェクトをキャンセルすることが実際にどうやって動作するかを理解するために、いくつかのサンプルプログラムに目を通していきましょう。 なお、この例や、このパートの他のコードを実行させるためには Twisted 10.1.0 かそれ以降のバージョン が必要です。 deferred-cancel/defer-cancel-1.py について考えてみましょう。

from twisted.internet import defer

def callback(res):
    print 'callback got:', res

d = defer.Deferred()
d.addCallback(callback)
d.cancel()
print 'done'

新しいキャンセル機能により、 Deferred クラスには cancel という新しいメソッドが追加されました。 このコード例では、遅延オブジェクトを新しく生成し、コールバックを付け加えて、発火させずに遅延オブジェクトをキャンセルします。 次のような出力になります。

done
Unhandled error in Deferred:
Traceback (most recent call last):
Failure: twisted.internet.defer.CancelledError:

遅延オブジェクトをキャンセルすることは、エラー用コールバックのチェーンを実行させるかのようですね。 そして、通常のコールバックは決して呼び出されません。 そのエラーは twisted.internet.defer.CancelledError であることにも注意しましょう。 これは、遅延オブジェクトがキャンセルされたことを意味する独自の Exception です (もう少し先まで読んで!)。 deferred-cancel/defer-cancel-2.py でエラー用コールバックを追加してみましょう。

from twisted.internet import defer

def callback(res):
    print 'callback got:', res

def errback(err):
    print 'errback got:', err

d = defer.Deferred()
d.addCallbacks(callback, errback)
d.cancel()
print 'done'

実行させてみると、このような出力になります。

errback got: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.defer.CancelledError'>:]
done

つまり、他のどのような遅延オブジェクトの失敗とも同じように、キャンセルからのエラー用コールバックを ‘catch’ できます。 deferred-cancel/defer-cancel-3.py にあるように、遅延オブジェクトを発火させてから、キャンセルしてみましょう。

from twisted.internet import defer

def callback(res):
    print 'callback got:', res

def errback(err):
    print 'errback got:', err

d = defer.Deferred()
d.addCallbacks(callback, errback)
d.callback('result')
d.cancel()
print 'done'

ここでは callback メソッドを使って普通に遅延オブジェクトを発火させ、キャンセルします。 出力です。

callback got: result
done

コールバックは呼び出され (予想通りですね)、プログラムは普通に終了しました。あたかも cancel は呼ばれなかったかのようですね。 ということで、遅延オブジェクトが発火済みだと、キャンセルすることは影響がないように見えます。(でも、もう少し先まで読んで!).

deferred-cancel/defer-cancel-4.py にあるように、キャンセルした「後」に遅延オブジェクトを発火させるとどうでしょう?

from twisted.internet import defer

def callback(res):
    print 'callback got:', res

def errback(err):
    print 'errback got:', err

d = defer.Deferred()
d.addCallbacks(callback, errback)
d.cancel()
d.callback('result')
print 'done'

そのような場合には、次の出力になります。

errback got: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.defer.CancelledError'>:]
done

おもしろいですね! ふたつ目の例とまったく同じ出力です。遅延オブジェクトをまったく発火させていませんでしたね。 遅延オブジェクトがキャンセル済みだと、それを発火させても普通は何の影響もありません。 しかし、遅延オブジェクトを一回以上発火できるはずで、エラー用コールバックチェーンは明らかに動作しているのに、 どうして d.callback('result') はエラーを発生させなかったのでしょうか?

もう一度”図39:キャンセルも含んだ遅延オブジェクトにおける情報の伝わり方”について考えてみましょう。 正常な結果あるいは失敗を与えて遅延オブジェクトを発火させることは、低レベルコードの仕事です。 一方で、遅延オブジェクトのキャンセルは、高レベルコードによって実行されます。 遅延オブジェクトを発火させることは「これが結果ですよ」という意味です。 一方で、遅延オブジェクトをキャンセルすることは「これ以上は何もいらないよ」という意味です。 そして、キャンセルは新しい機能であることを思い出してください。 既存のほとんどの Twisted のコードは、キャンセル操作を処理するようには記述されていません。 しかし、Twisted の開発者たちはいかなる遅延オブジェクトもキャンセルできるようにしました。 たとえそのコードが Twisted 10.1.0 以前に書かれたものであっても。

この実現において、 cancel メソッドは実際にはふたつのことを行います。

  1. Deferred オブジェクト自身に、まだ発生していなければ (つまり、遅延オブジェクトが発火されていなければ) 結果はいらないと伝え、後続するいかなる callbackerrback の呼び出しも無視します。

  2. そして、その影響として、結果を生成している最中の低レベルのコードに、操作をキャンセルするために要求されるすべてのステップを踏むようにと伝えます。

古い Twisted のコードは先へ先へと進み、キャンセルされた遅延オブジェクトも構わずに発火させますので、 ステップ1は、もしも古いライブラリから受け取った遅延オブジェクトをキャンセルしても、私たちのプログラムが台無しにならないことを保証してくれます。

このことは、私たちはいつも遅延オブジェクトのキャンセルに気を払わなくとも良いことを意味します。 結果が届かなければ (たとえ後で届くとしても)、それはもう受け取らないと確信できます。 しかし、遅延オブジェクトのキャンセルは、実は非同期操作のキャンセルではないかもしれません。 非同期操作を中止するにはコンテキストに特有の処理を必要とします。 ネットワーク接続を閉じる必要があるかもしれませんし、データベースのトランザクションをロールバックする、あるいはサブプロセスを kill する、などなど。 また、遅延オブジェクトは汎用的なコールバックを構成するためのものですから、キャンセルするときに特定の処理を行うことを教えるにはどうしましょうか? もしくはその代わりに、最初に遅延オブジェクトが生成されて返される低レベルのコードに、キャンセル要求を転送することはできるでしょうか? 今はこう言わせてください。

知ってるさ、コールバックを使うんだ!

遅延オブジェクトをキャンセルする、本当に

それじゃあ deferred-cancel/defer-cancel-5.py を見ていきましょう。

from twisted.internet import defer

def canceller(d):
    print "I need to cancel this deferred:", d

def callback(res):
    print 'callback got:', res

def errback(err):
    print 'errback got:', err

d = defer.Deferred(canceller) # created by lower-level code
d.addCallbacks(callback, errback) # added by higher-level code
d.cancel()
print 'done'

このコードは基本的に二番目の例と同じです。 みっつ目のコールバック (canceller) があることを除けば。これは、後で追加されるのではなく、生成されたときに Deferred へ渡されます。 このコールバックは、非同期操作を停止させる (もちろん、遅延オブジェクトが実際にキャンセルされたときのみです) ために必要となるコンテキスト特有のアクションを実行することに責任を持ちます。 canceller コールバックは必然的に遅延オブジェクトを返す低レベルのコードの一部です。 遅延オブジェクトを受け取って自分自身のコールバックとエラー用コールバックを追加するような高レベルのコードではありません。 サンプルコードを実行させて、次の出力を確認しましょう。

I need to cancel this deferred: <Deferred at 0xb7669d2cL>
errback got: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.defer.CancelledError'>:]
done

ご覧のように、 canceller コールバックは、もはや結果を必要としない遅延オブジェクトを与えられます。 この部分は、非同期操作を停止するために必要な、いかなるアクションも受け取れるところなのです。 canceller は、エラー用コールバックのチェーンが発火するより前に呼び出されていることに注意しましょう。 実際この時点では、私たちが選択する何らかの結果あるいはエラーを与えて、私たち自身の遅延オブジェクトを発火するかもしれません (それゆえ、 CancelledError の失敗を先取りします)。 どちらの可能性も deferred-cancel/defer-cancel-6.pydeferred-cancel/defer-cancel-7.py で説明されます。

reactor を立ち上げる前に、もうひとつ簡単なテストをやってみましょう。 canceller コールバックを持つ遅延オブジェクトを生成し、普通に発火させてからキャンセルします。 コードは deferred-cancel/defer-cancel-8.py にあります。 スクリプトの出力を検証することによって、遅延オブジェクトが発火された後にキャンセルすることは canceller コールバックを呼び出すことではない、と分かります。 キャンセルすべきものがありませんので、想定した通りです。

ここまで私たちが見てきた例は、実際のところ非同期操作ではありませんでした。 非同期操作を呼び出す簡単なプログラムを作成しましょう。操作をキャンセル可能にする方法が分かるはずです。 deferred-cancel/defer-cancel-9.py のコードについて考えます。

from twisted.internet.defer import Deferred

def send_poem(d):
    print 'Sending poem'
    d.callback('Once upon a midnight dreary')

def get_poem():
    """Return a poem 5 seconds later."""
    from twisted.internet import reactor
    d = Deferred()
    reactor.callLater(5, send_poem, d)
    return d

def got_poem(poem):
    print 'I got a poem:', poem

def poem_error(err):
    print 'get_poem failed:', err

def main():
    from twisted.internet import reactor
    reactor.callLater(10, reactor.stop) # stop the reactor in 10 seconds

    d = get_poem()
    d.addCallbacks(got_poem, poem_error)

    reactor.run()

main()

この例には、 get_poem が呼び出されてから5秒後に非同期に詩を返すために reactor の callLater メソッドを使う、 get_poem (send_poem の間違い?) 関数があります。 main 関数は get_poem を呼び出し、コールバックとエラー用コールバックのペアを追加します。それから reactor を開始させます。 10秒で reactor を停止させるための設定もしておきます (もう一度 callLater を使って)。 普通に遅延オブジェクトにコールバックを割り当てることによってこれを実装しますが、なんでこうするかを簡潔に確認しておきましょう。

コード例を実行させると、次の出力になります (適切な待ち時間の後で)。

Sending poem
I got a poem: Once upon a midnight dreary

そして10秒後にプログラムは停止します。 詩を送信される前に、この遅延オブジェクトをキャンセルしてみましょう。 10秒後に遅延オブジェクトをキャンセルするために、ちょっとしたコードを追加します (詩自体が5秒遅れる前です)。

reactor.callLater(2, d.cancel) # cancel after 2 seconds

プログラムの全体は deferred-cancel/defer-cancel-10.py にあります。 この出力は以下のようになります。

get_poem failed: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.defer.CancelledError'>:]
Sending poem

この例ははっきりと、遅延オブジェクトをキャンセルすることは、必ずしもその根底にある非同期リクエストをキャンセルしない、ということを描写しています。 2秒後にエラー用コールバックからの出力がありますね。期待した通りに CancelledError を表示します。 しかしそれでも、5秒後に send_poem からの出力が見えます (けれども、遅延オブジェクトのコールバックは発火しません)。

この時点では、 deferred-cancel/defer-cancel-4.py にあるのと同じ状況になっただけです。 遅延オブジェクトを「キャンセルする」ことは、結局は結果がキャンセルされるようになりますが、本当のところで操作を停止はしないのです。 ここまでで学んだように、本当にキャンセル可能な遅延オブジェクトを作成するためには、遅延オブジェクトが生成されたときに cancel コールバックを追加しなければなりません。

この新しいコールバックは何をする必要があるのでしょうか? callLater メソッドの ドキュメント に目を通しましょう。 callLater の戻り値は異なるオブジェクトで、 IDelayedCall を実装しており、実行されている部分から遅らされた呼び出しを防止するために使える cancel メソッドを持ちます。

これはとてもシンプルですね。 修正したコードは deferred-cancel/defer-cancel-11.py にあります。 関連のある変更点はすべて get_poem 関数にあります。

def get_poem():
    """Return a poem 5 seconds later."""

    def canceler(d):
        # They don't want the poem anymore, so cancel the delayed call
        delayed_call.cancel()

        # At this point we have three choices:
        #   1. Do nothing, and the deferred will fire the errback
        #      chain with CancelledError.
        #   2. Fire the errback chain with a different error.
        #   3. Fire the callback chain with an alternative result.

    d = Deferred(canceler)

    from twisted.internet import reactor
    delayed_call = reactor.callLater(5, send_poem, d)

    return d

新しいバージョンでは、キャンセルコールバックで利用できるように、 callLater からの戻り値を保存します。 コールバックは delayed_call.cancel() を呼び出すだけです。 しかし、上述の議論のように、遅延オブジェクト自身を発火させることも選べるでしょう。 最新バージョンの例ではこのような出力になります。

get_poem failed: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.defer.CancelledError'>:]

お分かりのように、遅延オブジェクトはキャンセルされ、非同期操作は真の意味で中止されました (つまり send_poemprint 出力を見ることはありません)。

詩のプロキシ 3.0

導入部分で議論したように、詩のプロキシサーバーはキャンセル処理を実装するには格好の例です。 誰も要求していないことが分かったら、詩のダウンロードを中止できるようにしましょう (つまり、クライアントは詩を送る前に接続を閉じます)。 プロキシのバージョン 3.0 は、 twisted-server-4/poetry-proxy.py にありますが、遅延オブジェクトのキャンセルを実装しています。 最初の変更点は PoetryProxyProtocol にあります。

class PoetryProxyProtocol(Protocol):

    def connectionMade(self):
        self.deferred = self.factory.service.get_poem()
        self.deferred.addCallback(self.transport.write)
        self.deferred.addBoth(lambda r: self.transport.loseConnection())

    def connectionLost(self, reason):
        if self.deferred is not None:
            deferred, self.deferred = self.deferred, None
            deferred.cancel() # cancel the deferred if it hasn't fired

古いバージョンの PoetryProxyProtocol と比較してみます。 ふたつの大きな変更点があります。

  1. 必要になったら後でキャンセルできるように、 get_poem から受け取った遅延オブジェクトを保存します。

  2. 接続が閉じられたときに遅延オブジェクトをキャンセルします。 これは実際に詩を受け取った後に遅延オブジェクトもキャンセルすることに注意しましょう。 しかし、この例で分かったように、発火済みの遅延オブジェクトをキャンセルすることは何の影響も与えません。

今度は、遅延オブジェクトのキャンセルが本当に詩のダウンロードを中止しているかをはっきりさせる必要があります。 これには ProxyService を変更します。

class ProxyService(object):

    poem = None # the cached poem

    def __init__(self, host, port):
        self.host = host
        self.port = port

    def get_poem(self):
        if self.poem is not None:
            print 'Using cached poem.'
            # return an already-fired deferred
            return succeed(self.poem)

        def canceler(d):
            print 'Canceling poem download.'
            factory.deferred = None
            connector.disconnect()

        print 'Fetching poem from server.'
        deferred = Deferred(canceler)
        deferred.addCallback(self.set_poem)
        factory = PoetryClientFactory(deferred)
        from twisted.internet import reactor
        connector = reactor.connectTCP(self.host, self.port, factory)
        return factory.deferred

    def set_poem(self, poem):
        self.poem = poem
        return poem

もう一度 古いバージョンの PoetryService と比較してみましょう。 このクラスにはさらにいくつかの変更点があります。

  1. reactor.connectTCP の戻り値を保存します。 保存したデータは IConnector オブジェクトです。 接続を閉じるためにこのオブジェクトの disconnect メソッドを使えます。

  2. canceler コールバックを持った遅延オブジェクトを作成します。 このコールバックは、接続を閉じるために connector を使うクロージャーです。 しかしまずは、 factory.deferred 属性に None を設定します。 さもなければ、遅延オブジェクト自身が CancelledError を伴って発火する前に、ファクトリは “connection closed” というエラー用コールバックを渡して遅延オブジェクトを発火させるかもしれません。 この遅延オブジェクトはキャンセルされましたので、 CancelledError と一緒に遅延オブジェクトを発火させることがより明示的になります。

今回は PoetryClientFactory ではなく ProxyService で遅延オブジェクトを生成していることに気付いたでしょうか。 キャンセラーコールバックは IConnector オブジェクトにアクセスする必要がありますので、遅延オブジェクトを生成するには ProxyService がもっとも便利な場所だ、といえます。

そしてここまでの例のひとつにあるように、 canceler コールバックはクロージャーとして実装されています。 キャンセルコールバックを実装するときにクロージャーはとても便利にみえますね!

それでは、新しいプロキシを試してみましょう。 まずは slow サーバを起動します。 ゆっくり動きますから、キャンセルするための時間は十分にあります。

python blocking-server/slowpoetry.py --port 10001 poetry/fascination.txt

プロキシを立ち上げましょう。 (Twisted 10.1.0 が必要であることを忘れないでくださいね)

python twisted-server-4/poetry-proxy.py --port 10000 10001

なんらかのクライアント、もしくは単に curl コマンドを使って、プロキシから詩をダウンロードし始めましょう。

curl localhost:10000

2, 3 秒後に, クライアントか curl プロセス を停止させるために Ctrl-C を押してください。 プロキシを実行しているターミナルで、この様な出力を確認できますね。

Fetching poem from server.
Canceling poem download.

プロキシがハングアップするため、 slow サーバは送信するそれぞれの詩に対する出力の表示を止めた、とみるべきでしょう。 クライアントを何回も開始と終了させて、その度にダウンロードがキャンセルされることを検証しましょう。 しかし、詩を完全にダウンロードさせてしまえば、プロキシはその詩をキャッシュして、それ以降は即座に送り返します。

驚くべきことをもうひとつ

ここまでで、発火済みの遅延オブジェクトをキャンセルしても何の影響もない、と何度か述べてきました。 ですが、これは実際には正しくありません。 “パート13: Deferred と行こう”で、遅延オブジェクトに追加されたコールバックとエラー用コールバックは遅延オブジェクト自身を返すかもしれない、と学びました。 この場合は、元の (外側の) 遅延オブジェクトはコールバックチェーンの処理を停止し、内側の遅延オブジェクトが発火するのを待ちます (“図28:外側と内側の遅延オブジェクトの処理の進み方”を見てください)。

したがって、コールバックチェーンは内側の遅延オブジェクトが終わるのを待った状態で停止させられますから、遅延オブジェクトが高レベルのコードを発火させたにも関わらず、作成した非同期リクエストは結果をまだ受け取っていないかもしれません。 そこで、高レベルのコードがその外側の遅延オブジェクトをキャンセルしたら何が起きるでしょうか? この場合は、外側の遅延オブジェクトは自分自身をキャンセルしません (結局、発火済みですから)。 その代わりに、外側の遅延オブジェクトは内側の遅延オブジェクトをキャンセルします。

遅延オブジェクトをキャンセルするときに、メインの非同期操作をキャンセルしていないかもしれません。 それよりむしろ、一番最初の結果として他の何らかの非同期操作を引き起こしたかもしれません。 ヒュー!

もうひとつの例でこれを具体化しましょう。 deferred-cancel/defer-cancel-12.py にあるコードについて考えます。

from twisted.internet import defer

def cancel_outer(d):
    print "outer cancel callback."

def cancel_inner(d):
    print "inner cancel callback."

def first_outer_callback(res):
    print 'first outer callback, returning inner deferred'
    return inner_d

def second_outer_callback(res):
    print 'second outer callback got:', res

def outer_errback(err):
    print 'outer errback got:', err

outer_d = defer.Deferred(cancel_outer)
inner_d = defer.Deferred(cancel_inner)

outer_d.addCallback(first_outer_callback)
outer_d.addCallbacks(second_outer_callback, outer_errback)

outer_d.callback('result')

# at this point the outer deferred has fired, but is paused
# on the inner deferred.

print 'canceling outer deferred.'
outer_d.cancel()

print 'done'

この例ではふたつの遅延オブジェクト、 outer_dinner_d 、を生成し、外側のコールバックのひとつは inner_d を返します。 最初に outer_d を発火させ、すぐにキャンセルします。 このサンプルコードの出力は次のようになります。

first outer callback, returning inner deferred
canceling outer deferred.
inner cancel callback.
outer errback got: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.defer.CancelledError'>:]
done

お分かりのように、外側の遅延オブジェクトをキャンセルしても、外側の遅延オブジェクトのキャンセルコールバックを発火させることにはつながりません。 そうではなく、内側の遅延オブジェクトのキャンセルコールバックを発火させますので、外側のエラー用コールバックは CancelledError を受け取ります (内側の遅延オブジェクトから)。

しばらくはこのコードを眺めて、いくつかの異なることが結果にどのような影響を与えるかを実験してみてください。

議論

遅延オブジェクトのキャンセルは、必要なくなった処理を回避できますので、非常に便利な操作になりえます。 一方でここまでで見てきたように、少しトリッキーともいえます。

心に留めておくべき重要なこととして、遅延オブジェクトをキャンセルしても必ずしも低レベルの非同期操作もキャンセルするわけではない、ということが挙げられます。 実際、この記事を書いている時点では、ほとんどの遅延オブジェクトが実際には “cancel” しません。 たいていの Twisted のコードは Twisted 10.1.0 より以前に記述されており、まだ更新が追いついていないからです。 Twisted 自体のたくさんの API も含まれいますよ! 遅延オブジェクトのキャンセルが本当にリクエストをキャンセルするか、それとも単に無視するのかを判別するために、ドキュメントとソースコードを確認してください。

重要なことの二つ目は、非同期 API から単純に遅延オブジェクトを返しても、文字通り完全にはキャンセル可能にはならない、ということです。 自分自身のプログラムでキャンセル処理を実装したいときは、さらなる例を求めて Twisted のソースコードを学ぶべきです。 キャンセル処理はまったく新しい機能ですから、パターンやベストプラクティスは今なお追及されています。

次は

これで Deferreds に関するすべてのことと、Twisted の背後にある主要なコンセプトを学びました。 つまり、紹介すべきことはこれ以上にそうそうありません。 Twisted の残りの部分は、Web プログラミングや非同期なデータベース接続のような、特定のアプリケーションを構築するためのものです。 ということで次の”パート20: 車輪の中の車輪: Twisted と Erlang”では、ちょっと回り道をして、非同期 I/O を使う他のふたつのシステムにも目を向けてみましょう。 Twisted における考え方と、それらの考え方にどのような関係があるのでしょう。 それが終わったら最後のパートで今までのまとめをして、Twisted の学習を続けるに当たってのお勧めの方法を考えましょう。

おすすめの練習問題

  • “canceled” の綴りがふたつ以上あることをご存知でしたか? 本当です 。 あなたの気分次第なのです。

  • キャンセルの実装に格別の注意を払いながら、 Deferred クラスのソースコードを熟読しましょう。

  • コールバックをキャンセルする遅延オブジェクトの例を Twisted 10.10 の ソースコード から探し、 実装を学んでください。

  • 詩のクライアントのひとつの get_poetry メソッドの戻り値である遅延オブジェクトを、キャンセルできるようにしてください。

  • reactor ベースのコード例を、内側の遅延オブジェクトで停止された外側の遅延オブジェクトをキャンセルするようにしてください。 callLater を使うと、外側の遅延オブジェクトが即座にキャンセルされること保証するために、注意深く delays を選択する必要があります。

  • Twisted の中で、本当のキャンセルをサポートしていない非同期 API を見つけて、それにキャンセルを実装してください。 できたら Twisted プロジェクトに パッチを送ってください 。 ユニットテストも忘れずに!