ボタンを押すと複数個所から複数データを拾ってきてそれぞれについて時間が掛かる処理を非同期で行うというコードをasync、await使って書いたら以下のようになったのですが、Clickイベント中にブロックされない様にTimer使っている部分が非常にダサいです。かつての、ユーザー定義メッセージをPostMessage()する的なことをしたかったのですが・・・。
どなたか、もっといい方法のご意見頂けますでしょうか。よろしくお願いいたします。
c#.net
1 private Queue<string> q2 = new Queue<string>(); 2 private Queue<string> q3 = new Queue<string>(); 3 4 private void button12_Click(object sender, EventArgs e) 5 { 6 string[] txt2 = { "ほげほげ", "にゃーにゃー" }; //どこかから複数データ拾ってくる 7 foreach(string s in txt2) 8 { 9 q2.Enqueue(s); //キューに入れる 10 } 11 timer2.Interval = 1; 12 timer2.Start(); //キューに入れといたからあとで処理しといて的な指示 13 //ブロックされたくないけどタイマー使うのダサい 14 15 string[] txt3 = { "ぼけぼけ", "フシャーフシャー", "ちゅーるちゅーる" }; //どこかから複数データ拾ってくる 16 foreach(string s in txt3) 17 { 18 q3.Enqueue(s); 19 } 20 timer3.Interval = 1; 21 timer3.Start(); 22 } 23 24 private bool reentryflg2 = false; //再入管理フラグ 25 private async void timer2_Tick(object sender, EventArgs e) 26 { 27 timer2.Stop(); 28 if (reentryflg2) { return; } //再入禁止 29 reentryflg2 = true; 30 31 while(q2.Count > 0) //キューが空になるまで 32 { 33 string s = q2.Dequeue(); 34 //別スレッドで実行し、終わるまでメインスレッドは一旦メッセージループに戻る 35 bool result = await Task.Run<bool>(()=>{return SpeekSubL(s); }); //左スピーカーで読み上げ 36 } 37 38 reentryflg2 = false; 39 } 40 41 private bool reentryflg3 = false; 42 private async void timer3_Tick(object sender, EventArgs e) 43 { 44 timer3.Stop(); 45 if (reentryflg3) { return; } 46 reentryflg3 = true; 47 48 while(q3.Count > 0) 49 { 50 string s = q3.Dequeue(); 51 bool result = await Task.Run<bool>(()=>{return SpeekSubR(s); }); //右スピーカーで読み上げ 52 } 53 54 reentryflg3 = false; 55 } 56
とりあえずまとめました。
●私の案1
キューに積んだ後タイマA、Bイベントでキュー処理開始予約だけしてメソッド抜ける。
タイマイベントではキュー内各項目を読み上げ、キューが空になったら終了。
読み上げのみ別スレッドで、読み上げ中はawaitでメッセージループに戻る。
タイマBイベントはタイマAイベントの最初のawait時に発生するので疑似マルチスレッド状態。
whileループもメインスレッドなので、Invoke使わず随時フォームとやり取りできる。
●YAmaGNZ氏案
キューに積んだ後サブスレッドA、Bが動いて無かったら動かしメソッド抜ける。
サブスレッドではキュー内各項目を読み上げ、キューが空になったら終了。
各項目の読み上げ開始をメインスレッドのフォームに表示させるにはInvokeでの通知必要。
●一般的な?Producer-Consumerパターン案
メインスレッド開始時にサブスレッドA、Bも起動。
以降メインスレッドはキューに積み続けるだけ。サブスレッドはBlockingCollection<>.Take()で待機してキューに入る度に読み上げの無限ループ。
各項目の読み上げ開始をメインスレッドのフォームに表示させるにはInvokeでの通知必要。
その上で、下記の様なものを作ってみました。こんな頻繁に別スレッド建てたり潰したりするより、複数スレッド並列でInvokeなりでやり取りしろよって気もしますが、ほぼメインスレッドだけなので割と気に入ってます。
●私の案2
メインスレッドのメッセージループを拡張する感じで、別スレッドでキュー待機し新着有ったら発動する処理を組み込んでおく。
以降キューに積み続けるだけ。
ほぼメインスレッドなので、Invoke使わず随時フォームとやり取りできる。
c#.net
1 private void Form1_Shown(object sender, EventArgs e) 2 { 3 //StartQueLoopL(); //直接呼んだらStartQueLoopR()にたどり着かない 4 timer2.Interval = 1; 5 timer2.Start(); //タイマ使ってメッセージループからStartQueLoopL()呼ばせる 6 StartQueLoopR(); //こっちは直接呼んでもいいがプログラム終了までShown抜けられないってヤダ 7 } 8 9 private void timer2_Tick(object sender, EventArgs e) 10 { 11 timer2.Stop(); 12 StartQueLoopL(); 13 } 14 15 //データを貯めとくキュー 16 private BlockingCollection<string> bcCueL = new BlockingCollection<string>(10); 17 private BlockingCollection<string> bcCueR = new BlockingCollection<string>(10); 18 19 private async void StartQueLoopL() 20 { 21 //最初に一回呼ばれるだけなので再入管理省略 22 23 while (true) 24 { 25 //別スレッドでキュー待機、その間一旦メッセージループに戻る 26 string txt = await Task.Run<string>((Func<string>)bcCueL.Take); 27 28 textBoxL.Text = $"猫「{txt}」"; //フォームいじれる 29 30 //別スレッドで読み上げ、終わるまで一旦メッセージループに戻る 31 bool result = await Task.Run<bool>(()=>{return SpeakSubL(txt); }); //左スピーカーで読み上げ 32 textBoxL.Text = ""; 33 } 34 } 35 36 private async void StartQueLoopR() 37 { 38 //最初に一回呼ばれるだけなので再入管理省略 39 40 while (true) 41 { 42 //別スレッドでキュー待機、その間一旦メッセージループに戻る 43 string txt = await Task.Run<string>((Func<string>)bcCueR.Take); 44 45 textBoxR.Text = $"人「{txt}」"; //フォームいじれる 46 47 //別スレッドで読み上げ、終わるまで一旦メッセージループに戻る 48 bool result = await Task.Run<bool>(()=>{return SpeakSubR(txt); }); //右スピーカーで読み上げ 49 textBoxR.Text = ""; 50 } 51 } 52 53 private void button12_Click(object sender, EventArgs e) 54 { 55 string[] txtL = { "フシャーフシャー", "にゃーにゃー" }; //どこかから複数データ拾ってくる 56 foreach(string s in txtL) 57 { 58 bcCueL.TryAdd(s); //キューに入れる 59 } 60 61 string[] txtR = { "ほげほげ", "ちゅーるちゅーる" }; //どこかから複数データ拾ってくる 62 foreach(string s in txtR) 63 { 64 bcCueR.TryAdd(s); 65 } 66 } 67
ただ、やはりTimerが必要になってしまいました。表題の件、いい方法ないですかね?
要は、Control.Refresh()したら描画メッセージが積まれ、メソッド抜けた後描画イベントが実行されるのと同じような事をユーザー定義メッセージでしたいだけなのですが。
調べても、「イベントを発生させるにはイベントハンドラを呼べ」とか「PerformClick()だ」とかばかりで・・・。下記1と2だと、クリックイベントの発生タイミングが違うんですが・・・。その場で呼びたいわけじゃなく、メソッド抜けた後メッセージループから呼ばせたいのです。
c#.net
1//1 2Thread.Sleep(10000); //この5秒目に(固まってる)ボタンを押す 3 4//2 5Thread.Sleep(5000); 6button1.PerformClick(); 7Thread.Sleep(5000);
NativeWindowクラス等使って無理やりやるしかないんですかね?
回答2件
あなたの回答
tips
プレビュー