asyncを使った非同期メソッドの実装 の履歴(No.1)
更新- 履歴一覧
- 差分 を表示
- 現在との差分 を表示
- ソース を表示
- プログラミング/C#/asyncを使った非同期メソッドの実装 へ行く。
- 1
Run ボタンを押している間動作を繰り返す†
フォームにボタンスタイルのチェックボックスと、結果表示用のリストボックスを配置し、 ボタンを押すと、もう一度押して解除するまでリストボックスにテキストを 追加し続けるアプリケーションを作ってみます。
次のような安易なコードを書いてしまうと、 リストボックスにテキストを表示し続けている間に ボタンの押下を検出できないため、 アプリケーションがデッドロックしてしまいます。
LANG:c# // これはうまく動かない private void checkBox1_CheckedChanged(object sender, EventArgs e) { if (!checkBox1.Checked) return; var i = 0; while (checkBox1.Checked) { // ボタンが解除されるまで listBox1.Items.Insert(0, (i++).ToString()); // 1行追加して Task.Delay(100).Wait(); // 100ms 待つ } listBox1.Items.Insert(0, "*** stop ***"); }
昔ながらの方法であれば while 内で Application.DoEvents() を呼んでお茶を濁したり、
- ボタンが押されたらスレッドを起動
- サブのスレッド内からは listBox にアクセスできないためWindowsメッセージでやりとりする
- もう一度ボタンが押されたらスレッドを止める
などとしそうなところですが、async, await, Invoke などを使うことで 上記の安易なコードそっくり(?)のコードを問題なく動かせました。
LANG:c# private async void checkBox1_CheckedChanged(object sender, EventArgs e) { if (!checkBox1.Checked) return; await Task.Run(()=>{ // スレッドを起動し、終了を待たずに戻る var i = 0; // checkBox1.Checked を確認するのに Invoke を通す while ((bool)Invoke(new Func<bool>(() => checkBox1.Checked))) { // listBox1 にテキストを追加するのに Invoke を通す Invoke(new Action(() => { listBox1.Items.Insert(0, (i++).ToString()); })); Task.Delay(100).Wait(); } }); listBox1.Items.Insert(0, "*** stop ***"); }
ミソとなるのは、
- 内部で await するメソッドには async を付けておく
- await Task.Run() によりサブスレッドを作成し実行
- async メソッドからはサブスレッドを起動した直後に呼び出し元に戻る
- サブスレッドからメインスレッド上のコントロールにアクセスするためには Invoke を利用する
- await から戻った後のコードはメインスレッドで実行されるので、 Invoke を使わずにコントロールにアクセス可能
というところになります。
以下はオフトピック†
Invoke を呼ぶ呪文†
"(bool)Invoke(new Func<bool>*1)=>..." や "Invoke(new Action*2)=>{...};" が冗長と思う場合には、Form の内部で
LANG:c# T Invoke<T>(Func<T> func) { return (T)base.Invoke(func); } void Invoke(Action action) { base.Invoke(action); }
としておけば、それぞれ "Invoke(()=>...)" や "Invoke(()=>{...});" で済むようです。
チェックボックスをトグルボタンとして使う†
LANG:c# checkBox1.Appearance = System.Windows.Forms.Appearance.Button; checkBox1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; checkBox1.UseVisualStyleBackColor = true;
とすることでトグル動作するボタンを作れます。
コメント†
Counter: 20041 (from 2010/06/03),
today: 4,
yesterday: 12