deferred.el の出来るまで:Emacsでの非同期処理

Emacsでの非同期処理は大変

これまでEmacsGUIアプリをいくつか書いてみて、非同期の処理を何とかしたいと思ったことが deferred.el を書こうと思った動機です。

Emacs Lisp上でコマンドを非同期で処理しようと思うと、コールバックをつなげたり、正しくエラー処理を行うために、かなり長いコードを書く必要があります。また、最近はWebにアクセスする機会も多いのですが、これも非同期で処理するとなるとまた面倒です。非同期をやめて、ブロックする(Emacs全体が固まる)コードで書けば楽なのですが、それではユーザー体験としてマイナスになってしまいます。

特に大変だったのが cacoo.el を作っているときでした。 cacoo.el では、以下のような流れで画像を表示します。

  • Webから画像取得(wget
  • 画像サイズ取得(identify)
  • 画像リサイズ(convert)
  • 表示(Emacs

また、オフラインでの動作も考えて、ローカルにキャッシュも持っています。個人的に大量の画像を処理することが多いので、非同期で処理することは必須でした。

これを普通に実装するのは大変なので、cacoo.elを最初に作った時は、簡単な非同期の仕組みを作って実装していました。アドホックでかなり簡単な仕組みだったにもかかわらず、全体のコードのうち相当な行数を占めてしまいました。こういうよくある処理のために毎回非同期の仕組みを準備するのは大変です。

同じシングルスレッドのJavaScriptでdeferredの仕組みが大成功していましたので、同じような仕組みがEmacsにもあるべきだと考えました。

Emacsの非同期処理について調べてみた

いきなり作る前に、まずは調査してみることにしました。その道30年のエディタです。もしかして、自分がEmacsでのうまい非同期の処理方法を知らないだけかもしれないと思い、ローカルのファイルやネット上のリソースを調べてみました。

普通の非同期・同期のまとめ

まず、Emacs上での標準的な方法についてまとめます。

  • 外部コマンド起動には同期(call-process)と非同期(start-process)がある
    • 同期はブロックする
    • 非同期はブロックせずに結果をフィルター関数(コールバックみたいなもの)やバッファで受け取る
  • TCPソケットも非同期プロセスと同様に扱う
  • 非同期処理が連続したり、多数の非同期プロセスの制御が難しい
    • コールバックの連続、待ち合わせ、起動プロセス数の制御など

以下の調査の目的は、この標準の方法以外の、うまい非同期の扱い方をしているものを見つけることです。
java の concurrent パッケージとか、 Mochikit.Async(JSDeferred) のようなものを見つけられればベストです。

ローカルのソースを探してみた
  • infoをasyncで検索
    • Asynchronous Processes の章
    • →普通のコマンド起動の話
  • 標準添付の elispgrep async してみた
    • →参考になりそうな結果は出てこなかった
  • 自分で入れた site-lispgrep async してみた
    • jdeのlisp/beanshell.el
      • 非同期でJavaとやり取り
      • 普通にfilterで受け取っている
    • semantic.elの [async parse operation]
      • idle時間中にパースをちょこっと行う
      • idle time はちょっと違う
    • wl
      • そもそも各地でブロックしている
    • emacs-w3m
      • w3m-proc.elで非同期用のしくみやマクロを定義
        • w3m-do-processというマクロで非同期とその結果の処理をうまく書ける
        • 同期でもインタフェースは変わらない
        • 独自のキューで同時実行数を制御している
        • 外部コマンドの実行がメイン
        • 非同期の直列化は可能だけども、並列や待ち合わせなどはなし
ネットで検索してみた

教科書どおりに「Emacsと非同期」「Emacs async」で検索して、出てきた結果を調べてみました。

なかなか、基本以外のうまいやり方が出てきません。
今度は非同期を扱ってそうなアプリを調査してみました。

  • auto-install.el
    • url-retrieveで非同期にバッファで取ってくる
  • twittering-mode
    • 自分でTCPで接続したり、Curl(SSL)でとってくる
    • OAuthの実装など
  • navi2ch
    • navi2ch-net.el
    • 同期で自力HTTP実装
    • 細かくデータを取る工夫や、落ちてるサーバーが多いためか、細かな制御が実装してある
    • HTTP自力実装(認証、Proxy、Cookie、部分ロード)の参考になるかも
    • ※@naotaさんによると、当初は移植性を考えて独自実装になっているとのこと
  • async-eval.el
    • 外部プロセスのEmacsを起動して、引数でelispを渡して結果を受け取る
    • qで起動しているので思ったより速い
    • 簡易スレッドとして使えそう
    • プロセス間で通信が実現できればもっと面白そう
  • comint.el
    • 標準添付、多数のシェル的アプリが利用
    • 普通にfilterで受け取っている
調査結果まとめ

結果、便利な非同期ライブラリは見つからず、みんな普通に頑張っていることが分かりました。見た中では、emacs-w3mの非同期マクロが一番頑張っていたように思います。

また、結構HTTPを自力で話すことが多いことも分かりました。歴史的な経緯から、環境依存を避けていることが原因かもしれません。どの環境でも wgetcurl が使えればかなり便利なのですが、Windowsのユーザーを対象にしたい場合はなかなか難しいかもしれません。

設計・実装へ

ということで、作るしかないということが分かりました。

作ると決まれば、どんな物を作るかについて考えるわけですが、どんなインタフェースが良いか、どんな実装にするべきかなど、いろいろ気になることが出てきます。JSDeferredがコードも短くてお手本にするには丁度いいと思っているのですが、もっと他に見ておくべき実装はないかどうか調べてみました。

次回、deferred.el の出来るまで:調査、設計・実装に続きます。