質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Windows Forms

Windows Forms(WinForms)はMicrosoft .NET フレームワークに含まれる視覚的なアプリケーションのプログラミングインターフェイス(API)です。WinFormsは管理されているコードの既存のWindowsのAPIをラップすることで元のMicrosoft Windowsのインターフェイスのエレメントにアクセスすることができます。

イベントハンドラ

マウスのクリックなどの特定の事象(イベント)が発生した時に実行される処理のことをイベントハンドラと呼びます。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

.NET Framework 4.0

Microsoft Windows用のソフトウェア開発環境/実行環境である .NET Frameworkの4番目のメジャーバージョンです。

Q&A

解決済

2回答

7546閲覧

c#.netでPostMessage()的な事

zonozono

総合スコア16

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Windows Forms

Windows Forms(WinForms)はMicrosoft .NET フレームワークに含まれる視覚的なアプリケーションのプログラミングインターフェイス(API)です。WinFormsは管理されているコードの既存のWindowsのAPIをラップすることで元のMicrosoft Windowsのインターフェイスのエレメントにアクセスすることができます。

イベントハンドラ

マウスのクリックなどの特定の事象(イベント)が発生した時に実行される処理のことをイベントハンドラと呼びます。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

.NET Framework 4.0

Microsoft Windows用のソフトウェア開発環境/実行環境である .NET Frameworkの4番目のメジャーバージョンです。

0グッド

0クリップ

投稿2019/07/09 19:03

編集2019/07/11 21:33

ボタンを押すと複数個所から複数データを拾ってきてそれぞれについて時間が掛かる処理を非同期で行うというコードを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クラス等使って無理やりやるしかないんですかね?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YAmaGNZ

2019/07/11 22:25

いまいちよく分からないのですが、何故Invokeをさけて構造を複雑にしようとしているのでしょうか? Invokeを使わない利点?よりメインスレッドに永久ループがある欠点のほうが大きい気がします。
zonozono

2019/07/11 23:28

私も同感なのですが、発生した疑問箇所は解決しておきたく思っております。 その後調べまして、ユーザー定義メッセージについては、WndProcのオーバー ライドで出来そうな気配です。 Controlクラスを派生したAnnouncerクラスを作って・・・って感じでしょうか。 もっと簡単な方法があればよかったのですが・・・。 ただそれ以前に、上記コードのForm1_Shown()内でタイマ使わずStartQueLoopL() とStartQueLoopR()直接呼んでもStartQueLoopR()にたどり着けちゃいました。 awaitで飛ぶ先はフォームのメッセージループで、StartQueLoopL()内のwhile (true) ループを抜けない限りStartQueLoopR()にたどり着けないと思っていたのですが・・・ 私何か大きな勘違いしてますでしょうか?
zonozono

2019/07/12 00:05

あ、非同期メソッドをawait付けずに呼び出したから待機しなかったってことでした。 するとそれで解決したのかな? 改めて見てみると…確かにややこしいです。面白いけど。 ライブラリに入れるならやはり最初から別スレッド作ってInvokeでやり取りする方ですかね。検討してみます。
guest

回答2

0

ベストアンサー

処理実行中にもう一度行わないのなら、button12_Click内にて下記みたいな感じで判断して実行しないのはダメなんですか?

C#

1private Task t2,t3; 2private void button12_Click() 3{ 4 string[] txt2 = { "ほげほげ", "にゃーにゃー" }; //どこかから複数データ拾ってくる 5 foreach (string s in txt2) 6 { 7 q2.Enqueue(s); //キューに入れる 8 } 9 10 if (t2 == null || t2.IsCompleted) 11 { 12 t2 = Task.Run(() => { 13 //タイマー内で行っている処理 14 }); 15 } 16 17 string[] txt3 = { "ぼけぼけ", "フシャーフシャー", "ちゅーるちゅーる" }; //どこかから複数データ拾ってくる 18 foreach (string s in txt3) 19 { 20 q3.Enqueue(s); 21 } 22 23 if (t3 == null || t3.IsCompleted) 24 { 25 t3 = Task.Run(() => { 26 //タイマー内で行っている処理 27 }); 28 } 29}

一応、もとのソースが、タスク実行中でもキューだけは積むような形になっていたのでそのままにしていますが、キューに積むのも行わないのであれば、ボタンを押せないようにすればいいだけだと思います。

追記

質問の最後にあることですが

C#

1Console.WriteLine("SleepStart"); 2System.Threading.Thread.Sleep(500); 3button1.PerformClick(); 4System.Threading.Thread.Sleep(500); 5Console.WriteLine("SleepEnd"); 6 7Console.WriteLine("SleepStart"); 8System.Threading.Thread.Sleep(500); 9this.BeginInvoke((Action)(() => button1.PerformClick())); 10System.Threading.Thread.Sleep(500); 11Console.WriteLine("SleepEnd"); 12 13private void button1_Click(object sender, EventArgs e) 14{ 15 Console.WriteLine("Click!!!"); 16} 17

こちらを試してみてください。

投稿2019/07/09 22:40

編集2019/07/12 01:03
YAmaGNZ

総合スコア10242

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

zonozono

2019/07/10 04:10

なるほど。覚えたてのasync-awaitを使うことばかり考えてましたが、その方がかなり良さそうです。 キューの出し入れがスレッド跨いでしまうのが気になりましたが、調べてみたら今どきはスレッドセーフなキューもあるようで…。 BlockingCollection<T>.Take()を使えば、最初に別スレッド立ち上げておいて後はキューに追加していくだけというやり方も出来そうです。色々試してみます。 非常に参考になりました。ありがとうございました。
zonozono

2019/07/12 12:20

おお!BeginInvokeでSleepEnd後にClick!!!されました! なるほど。 BeginInvokeは非同期実行だから同一スレッド上で呼んだら現在の処理終わった後実行されるというわけですか。 Invoke系は別スレッドある時使うと覚えてて、同一スレッド向けに使うこと考えたことありませんでした。 これは何かと役立ちそうです。覚えておきます&Task、async、await、Invoke辺りはもっとドキュメント読みこんでみようと思います。 これにてすべて解決です。ありがとうございました。
guest

0

キューやフラグを作る意図がよくわかりませんでしたが、

ボタンを押すと複数個所から複数データを拾ってきてそれぞれについて時間が掛かる処理を非同期で行う
Clickイベント中にブロックされない様に

という内容を見て判断するとClickイベント自体をasyncにしてキューやフラグを作らずシンプルに処理してあげれば良い気がします。

C#

1private async void button12_click (object sender, EventArgs e) 2{ 3 string[] txt2 = { "ほげほげ", "にゃーにゃー" }; //どこかから複数データ拾ってくる 4 foreach(string s in txt2){ 5 bool result = await Task.Run<bool>(()=>{return SpeekSubL(s); }); //左スピーカーで読み上げ 6 } 7 string[] txt3 = { "ぼけぼけ", "フシャーフシャー", "ちゅーるちゅーる" }; //どこかから複数データ拾ってくる 8 foreach(string s in txt2){ 9 bool result = await Task.Run<bool>(()=>{return SpeekSubR(s); }); //左スピーカーで読み上げ 10 } 11}

txt2やtxt3のデータのソースを取得するためにUIスレッドへのアクセスが必要な場合は、foreachの処理だけをTaskに逃がす、YAmaGNZさんの書いたコードが良いと思います。

例えばフォームやその配下のコントロールへのアクセスがそれに該当します。

投稿2019/07/10 04:27

BluOxy

総合スコア2663

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

zonozono

2019/07/11 23:00 編集

どうやら私がやろうとしてることはProducer-Consumerパターンというものらしいです。 つまり、表では情報集め続け、裏ではそれを処理し続けるという。 なので、キューを使っています。 そして今回はテストプログラムなのでクリックで情報追加しましたが、最終的にはタイマ・通知等で能動的受動的に情報集める形になります。 そのため、キューに入れた後は読み上げ開始の指示だけしておいてメソッドは速やかに抜けたかったのです。 ちなみにフラグ管理入れたのは再入禁止しないと多重奏始まってしまうからです。(LとRの二重奏はok) ご提案の方法ですと、awaitの飛び先はメッセージループなので、Lの再生が終わるまでRの再生が始まらなくなってしまうのがまずいです。※ 今、色々考慮した別案作成中ですので、それをupしたらまたご意見いただけますと幸いです。 ※もしかして私awaitからの処理の流れ勘違いしてるかもしれません…。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問