Polyphonic C#
Polyphonic C#とはなんぞや、という人もいるかも知れませんが、ちょっと前に話題になったCωの一部となった非同期処理に関するプロジェクトです。詳しくはサイトを見ていただくとして、実は、Nemerleでも同様の機能をライブラリとしてサポートしていたりします。Polyphonic C#の紹介ついでに解説してみようかと思います。
ぶっちゃけ、Polyphonic C#だと何が嬉しいのか?
非同期処理(マルチスレッド)を分かりやすく書くことが出来ます。非同期処理で何が問題になるというと状態管理ですが、通常は変数を用いて状態管理を行います。しかし、Polyphonic C#はchordと呼ばれるメソッドを使って状態管理するところが、特徴的だと言えます。メソッドで状態管理とは何のこっちゃ?かもしれませんが、しばらくお付き合いください。
良くある例として、2つのスレッドの一方が書き込んで、もう一方が読み込むという処理を考えてみます。データの状態は値がある/ないの2つを取るとします。書き込む側は値が「ない」時にしか書き込めません。読み込む側は値が「ある」時にしか読み込めません。
さて、実装ですが、まず、状態を表すメソッドを用意します。
- Empty () : 値がない
- Contains (s : string) : 値がある
また、このデータへの操作として、
- Put (s : string) : 値の設定
- Get () : 値の取得
このようなメソッドを用意することにしましょう。
PutとGetはいつでも使えるわけではなく、PutはEmpty状態の場合のみ呼びだせ、状態をContainsに遷移させます。一方、GetはContains状態の場合のみ呼び出せ、状態をEmptyに遷移させます。Nemerleで実装すると以下のようになります。
public class OnePlaceBuffer { public this () { Empty (); } [ChordMember] Empty () : void; [ChordMember] Contains (s : string) : void; public Put (s : string) : void chord { | Empty => Contains (s); } public Get () : string chord { | Contains => Empty (); s; } }
ソースコードを見ていきましょう。まず、コンストラクタ(this)で状態をEmptyにしています。Empty、Containsメソッドは状態を表すために使うのでChordMember属性を付け、実装は行いません。Putにはchordブロックがあり、そこでEmpty状態を待ちます。もし、Empty状態でない場合、このメソッドはブロックされます。Getも同様にContains状態になるのを待ちます。
では、このクラスを使ったサンプルコードを見てみましょう。
using System; using System.Threading; using Nemerle.Concurrency; public class OnePlaceBuffer { public this () { Empty (); } [ChordMember] Empty () : void; [ChordMember] Contains (s : string) : void; public Put (s : string) : void chord { | Empty => Contains (s); } public Get () : string chord { | Contains => Empty (); s; } } def b = OnePlaceBuffer (); // asyncブロックは非同期 async { // ノーウェイト for (mutable i = 0; i < 5; ++i) b.Put (i.ToString ()); // 1秒待つ for (mutable i = 0; i < 5; ++i) { b.Put (i.ToString ()); Thread.Sleep (1000); } } // 0.5秒待つ for (mutable i = 0; i < 10; ++i) { Console.WriteLine ($"$(b.Get ()) : $(DateTime.Now)"); Thread.Sleep (500); } /* 結果 0 : 2006/05/06 5:33:28 1 : 2006/05/06 5:33:28 2 : 2006/05/06 5:33:29 3 : 2006/05/06 5:33:29 4 : 2006/05/06 5:33:30 0 : 2006/05/06 5:33:30 1 : 2006/05/06 5:33:31 2 : 2006/05/06 5:33:32 3 : 2006/05/06 5:33:33 4 : 2006/05/06 5:33:34 */
処理としてはまず、Putメソッドをノーウェイトで5回呼び出し、その後、1秒間隔で5回呼んでいます。一方、Getメソッドは0.5秒間隔で10回呼び出しています。結果の時間をみると分かりますが、まず、最初はPutよりもGetの方が遅いので、Getに合わせて0.5秒間隔で処理が行われ、その後、GetよりもPutの方が遅くなるので1秒間隔で処理が行われます。
単純な例なので面倒なだけに感じるかも知れませんが、処理が複雑になれば有り難みが増すと思います。多分。(^^;