Perl-GTKならばCoroを使ってこうするだろうか。
use strict;
use warnings;
use Gtk2 '-init', '-threads-init';
use AnyEvent;
use Coro;
use Coro::Timer;
my $window = Gtk2::Window->new('toplevel');
$window->signal_connect(
destroy => sub {
exit;
}
);
my $vbox = Gtk2::VBox->new;
my $button = Gtk2::Button->new('click me');
$button->signal_connect(
clicked => sub {
$button->set_sensitive(0);
async {
Coro::Timer::sleep 3;
Gtk2::Gdk::Threads->enter;
$button->set_sensitive(1);
Gtk2::Gdk::Threads->leave;
};
}
);
$vbox->add($button);
$window->add($vbox);
$window->show_all;
Gtk2->main;
ここでCoro::Timer::sleep
しているのは、生のsleep
を使うとCoroがスケジュール割り当てとして介入出来なくなるのを回避する為。非同期で動作はするものの、スレッド動作になるので「Gtk2::Threads->enter
」と「Gtk2::Threads->leave
」でUIに関連する処理をブロックしなければなりません。つまり例えばボタン押下時の処理が5段階あり、その都度UIの何かしらを変更しなければならない場合、「Gtk2::Threads->enter
」と「Gtk2::Threads->leave
」でそれぞれブロックしなければならない事になります。平たく言うと、スレッドを使う予定の無かったUI処理を、スレッド対応にするには多少のコード修正が必要になるという事です。
今回、goで作った簡易twitterクライアントの「タイムライン更新」ボタンを非同期対応するのに費やしたコードはたった2行。元のコード
button.Clicked(func(w *gtk.GtkWidget, args []unsafe.Pointer) {
button.SetSensitive(false);
if r, _, err := http.Get("http://twitter.com/statuses/public_timeline.json"); err == nil {
// タイムライン取得、表示処理
}
button.SetSensitive(true);
}, nil);
となっていたコードに「go」で実行されるブロックを追加しただけ。
button.Clicked(func(w *gtk.GtkWidget, args []unsafe.Pointer) {
button.SetSensitive(false);
go func() {
if r, _, err := http.Get("http://twitter.com/statuses/public_timeline.json"); err == nil {
// タイムライン取得、表示処理
}
button.SetSensitive(true);
}();
}, nil);
何が言いたいのかというと、groutineはgo組み込みであって、Coroがsleepやhttp_getを置き換えようとしている(CORE::GLOBAL上書きもあるけど)物とは違い、言語として非同期をサポートしているという事。もちろんgoにおいてもボタンクリック時にsleepを呼ぶとUIはブロックされるのだけど、実際sleepなんて使わないし、そもそもgoroutineはスレッドじゃない。そう、goroutineはスレッドじゃない。
A Tutorial for the Go Programming Language実際にはgoが内部でscheduleを行って実行する処理連鎖で実現している。なのでgtkを呼び出す場合においてもgdk_threads_enter/gdk_threads_leaveを呼び出す必要もない。なぜなら並行とは言えど同時実行ではないのでコンテキストスイッチも意識しなくて良い。
Go has its own model of process/threads/light-weight processes/coroutines, so to avoid notational confusion we call concurrently executing computations in Go goroutines.
http://golang.org/doc/go_tutorial.html
故に上記の様な 「
go func(){}()
」ブロックだけで非同期処理が書けるという訳。例えばcursesでCUIアプリを書いても画面が崩れる事は無いだろう(未確認)。UIの開発に有利な言語にはある程度、共通する物があると思う。
- 匿名関数をコールバックとして扱える
- サードパーティでも良いので非同期処理の仕組みがある
- GCが効く
sub {}
」で匿名関数が使えるがので優秀だが非同期となるとスレッドに頼らざるを得ないのが難点。rubyも同様。pythonは匿名関数が使えないのが少し悲しい(でもpygtkは好きだ)。結局の所、メインループと非同期処理をどう扱うかがネックになっている。例えば各言語でCOMを扱う場合、STA(Single Thread Apartment)しか対応していないCOMは、言語側でメインループをグルグル回すしかない。まぁ言語として用途が違うんだ(JavascriptはそもそもUI記述言語だろ...とか)から、比べちゃならんだろ...とも言えるし。goにおいては上記3点あてはまるが、デストラクタが無いのが一番の難点。
これからのUIはどんどん非同期な物になって行くんだろうな。
10年程前のUIと言えば、非同期なUIはあまり見られなかった。VB6においても定期的に「
DoEvents
」を実行してプログレスバーを更新するという、なんちゃって非同期処理が横行していた頃だ。最近はVB.NETでコンポーネント自身がスレッドサポート(実際にはデリゲート)しているので簡単に非同期処理が書ける様になったが、これら全てにおいてもやはり非同期対応するつもりの無かったUI処理を、非同期対応にするには多少のコード修正が必要になるには違い無いし、その点でgoには期待している。