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

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

ただいまの
回答率

90.38%

  • C#

    9489questions

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

  • Windows Forms

    187questions

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

  • 非同期処理

    145questions

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

  • .NET Framework 4.0

    88questions

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

  • イベントハンドラ

    44questions

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

c#.netでPostMessage()的な事

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 183

zonozono

score 8

ボタンを押すと複数個所から複数データを拾ってきてそれぞれについて時間が掛かる処理を非同期で行うというコードをasync、await使って書いたら以下のようになったのですが、Clickイベント中にブロックされない様にTimer使っている部分が非常にダサいです。かつての、ユーザー定義メッセージをPostMessage()する的なことをしたかったのですが・・・。
どなたか、もっといい方法のご意見頂けますでしょうか。よろしくお願いいたします。

        private Queue<string> q2 = new Queue<string>();
        private Queue<string> q3 = new Queue<string>();

        private void button12_Click(object sender, EventArgs e)
        {
            string[] txt2 = { "ほげほげ", "にゃーにゃー" };        //どこかから複数データ拾ってくる
            foreach(string s in txt2)
            {
                q2.Enqueue(s);    //キューに入れる
            }
            timer2.Interval = 1;
            timer2.Start();   //キューに入れといたからあとで処理しといて的な指示
                                //ブロックされたくないけどタイマー使うのダサい

            string[] txt3 = { "ぼけぼけ", "フシャーフシャー", "ちゅーるちゅーる" };    //どこかから複数データ拾ってくる
            foreach(string s in txt3)
            {
                q3.Enqueue(s);
            }
            timer3.Interval = 1;
            timer3.Start();
        }

        private bool reentryflg2 = false;    //再入管理フラグ
        private async void timer2_Tick(object sender, EventArgs e)
        {
            timer2.Stop();
            if (reentryflg2) { return; }    //再入禁止
            reentryflg2 = true;

            while(q2.Count > 0)    //キューが空になるまで
            {
                string s = q2.Dequeue();
                //別スレッドで実行し、終わるまでメインスレッドは一旦メッセージループに戻る
            bool result = await Task.Run<bool>(()=>{return SpeekSubL(s); });    //左スピーカーで読み上げ
            }

            reentryflg2 = false;
        }

        private bool reentryflg3 = false;
        private async void timer3_Tick(object sender, EventArgs e)
        {
            timer3.Stop();
            if (reentryflg3) { return; }
            reentryflg3 = true;

            while(q3.Count > 0)
            {
                string s = q3.Dequeue();
            bool result = await Task.Run<bool>(()=>{return SpeekSubR(s); });    //右スピーカーで読み上げ
            }

            reentryflg3 = false;
        }

とりあえずまとめました。

●私の案1
キューに積んだ後タイマA、Bイベントでキュー処理開始予約だけしてメソッド抜ける。
タイマイベントではキュー内各項目を読み上げ、キューが空になったら終了。
読み上げのみ別スレッドで、読み上げ中はawaitでメッセージループに戻る。
タイマBイベントはタイマAイベントの最初のawait時に発生するので疑似マルチスレッド状態。
whileループもメインスレッドなので、Invoke使わず随時フォームとやり取りできる。

●YAmaGNZ氏案
キューに積んだ後サブスレッドA、Bが動いて無かったら動かしメソッド抜ける。
サブスレッドではキュー内各項目を読み上げ、キューが空になったら終了。
各項目の読み上げ開始をメインスレッドのフォームに表示させるにはInvokeでの通知必要。

●一般的な?Producer-Consumerパターン案
メインスレッド開始時にサブスレッドA、Bも起動。
以降メインスレッドはキューに積み続けるだけ。サブスレッドはBlockingCollection<>.Take()で待機してキューに入る度に読み上げの無限ループ。
各項目の読み上げ開始をメインスレッドのフォームに表示させるにはInvokeでの通知必要。

その上で、下記の様なものを作ってみました。こんな頻繁に別スレッド建てたり潰したりするより、複数スレッド並列でInvokeなりでやり取りしろよって気もしますが、ほぼメインスレッドだけなので割と気に入ってます。

●私の案2
メインスレッドのメッセージループを拡張する感じで、別スレッドでキュー待機し新着有ったら発動する処理を組み込んでおく。
以降キューに積み続けるだけ。
ほぼメインスレッドなので、Invoke使わず随時フォームとやり取りできる。

        private void Form1_Shown(object sender, EventArgs e)
        {
            //StartQueLoopL();    //直接呼んだらStartQueLoopR()にたどり着かない
            timer2.Interval = 1;
            timer2.Start();            //タイマ使ってメッセージループからStartQueLoopL()呼ばせる
            StartQueLoopR();        //こっちは直接呼んでもいいがプログラム終了までShown抜けられないってヤダ
        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            timer2.Stop();
            StartQueLoopL();
        }

        //データを貯めとくキュー
        private    BlockingCollection<string> bcCueL = new BlockingCollection<string>(10);
        private    BlockingCollection<string> bcCueR = new BlockingCollection<string>(10);

        private async void StartQueLoopL()
        {
            //最初に一回呼ばれるだけなので再入管理省略

            while (true)
            {
                //別スレッドでキュー待機、その間一旦メッセージループに戻る
                string txt = await Task.Run<string>((Func<string>)bcCueL.Take);

                textBoxL.Text = $"猫「{txt}」";    //フォームいじれる

                //別スレッドで読み上げ、終わるまで一旦メッセージループに戻る
            bool result = await Task.Run<bool>(()=>{return SpeakSubL(txt); });  //左スピーカーで読み上げ
                textBoxL.Text = "";
            }
        }

        private async void StartQueLoopR()
        {
            //最初に一回呼ばれるだけなので再入管理省略

            while (true)
            {
                //別スレッドでキュー待機、その間一旦メッセージループに戻る
                string txt = await Task.Run<string>((Func<string>)bcCueR.Take);

                textBoxR.Text = $"人「{txt}」";    //フォームいじれる

                //別スレッドで読み上げ、終わるまで一旦メッセージループに戻る
            bool result = await Task.Run<bool>(()=>{return SpeakSubR(txt); });    //右スピーカーで読み上げ
                textBoxR.Text = "";
            }
        }

        private void button12_Click(object sender, EventArgs e)
        {
            string[] txtL = { "フシャーフシャー", "にゃーにゃー" };        //どこかから複数データ拾ってくる
            foreach(string s in txtL)
            {
                bcCueL.TryAdd(s);    //キューに入れる
            }

            string[] txtR = { "ほげほげ", "ちゅーるちゅーる" };    //どこかから複数データ拾ってくる
            foreach(string s in txtR)
            {
                bcCueR.TryAdd(s);
            }
        }

ただ、やはりTimerが必要になってしまいました。表題の件、いい方法ないですかね?
要は、Control.Refresh()したら描画メッセージが積まれ、メソッド抜けた後描画イベントが実行されるのと同じような事をユーザー定義メッセージでしたいだけなのですが。

調べても、「イベントを発生させるにはイベントハンドラを呼べ」とか「PerformClick()だ」とかばかりで・・・。下記1と2だと、クリックイベントの発生タイミングが違うんですが・・・。その場で呼びたいわけじゃなく、メソッド抜けた後メッセージループから呼ばせたいのです。

//1
Thread.Sleep(10000);    //この5秒目に(固まってる)ボタンを押す

//2
Thread.Sleep(5000);
button1.PerformClick();
Thread.Sleep(5000);

NativeWindowクラス等使って無理やりやるしかないんですかね?

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • YAmaGNZ

    2019/07/12 07:25

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

    キャンセル

  • zonozono

    2019/07/12 08:28

    私も同感なのですが、発生した疑問箇所は解決しておきたく思っております。

    その後調べまして、ユーザー定義メッセージについては、WndProcのオーバー
    ライドで出来そうな気配です。
    Controlクラスを派生したAnnouncerクラスを作って・・・って感じでしょうか。
    もっと簡単な方法があればよかったのですが・・・。

    ただそれ以前に、上記コードのForm1_Shown()内でタイマ使わずStartQueLoopL()
    とStartQueLoopR()直接呼んでもStartQueLoopR()にたどり着けちゃいました。
    awaitで飛ぶ先はフォームのメッセージループで、StartQueLoopL()内のwhile (true)
    ループを抜けない限りStartQueLoopR()にたどり着けないと思っていたのですが・・・
    私何か大きな勘違いしてますでしょうか?

    キャンセル

  • zonozono

    2019/07/12 09:05

    あ、非同期メソッドをawait付けずに呼び出したから待機しなかったってことでした。
    するとそれで解決したのかな?

    改めて見てみると…確かにややこしいです。面白いけど。
    ライブラリに入れるならやはり最初から別スレッド作ってInvokeでやり取りする方ですかね。検討してみます。

    キャンセル

回答 2

checkベストアンサー

+2

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

private Task t2,t3;
private void button12_Click()
{
    string[] txt2 = { "ほげほげ", "にゃーにゃー" };        //どこかから複数データ拾ってくる
    foreach (string s in txt2)
    {
        q2.Enqueue(s);    //キューに入れる
    }

    if (t2 == null || t2.IsCompleted)
    {
        t2 = Task.Run(() => {
            //タイマー内で行っている処理
        });
    }

    string[] txt3 = { "ぼけぼけ", "フシャーフシャー", "ちゅーるちゅーる" };    //どこかから複数データ拾ってくる
    foreach (string s in txt3)
    {
        q3.Enqueue(s);
    }

    if (t3 == null || t3.IsCompleted)
    {
        t3 = Task.Run(() => {
            //タイマー内で行っている処理
        });
    }
}


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

追記

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

Console.WriteLine("SleepStart");
System.Threading.Thread.Sleep(500);
button1.PerformClick();
System.Threading.Thread.Sleep(500);
Console.WriteLine("SleepEnd");

Console.WriteLine("SleepStart");
System.Threading.Thread.Sleep(500);
this.BeginInvoke((Action)(() => button1.PerformClick()));
System.Threading.Thread.Sleep(500);
Console.WriteLine("SleepEnd");

private void button1_Click(object sender, EventArgs e)
{
    Console.WriteLine("Click!!!");
}


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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/07/10 13:10

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

    非常に参考になりました。ありがとうございました。

    キャンセル

  • 2019/07/12 21:20

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

    これにてすべて解決です。ありがとうございました。

    キャンセル

+1

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

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

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/07/12 03:33 編集

    どうやら私がやろうとしてることはProducer-Consumerパターンというものらしいです。 つまり、表では情報集め続け、裏ではそれを処理し続けるという。

    なので、キューを使っています。 そして今回はテストプログラムなのでクリックで情報追加しましたが、最終的にはタイマ・通知等で能動的受動的に情報集める形になります。 そのため、キューに入れた後は読み上げ開始の指示だけしておいてメソッドは速やかに抜けたかったのです。
    ちなみにフラグ管理入れたのは再入禁止しないと多重奏始まってしまうからです。(LとRの二重奏はok)

    ご提案の方法ですと、awaitの飛び先はメッセージループなので、Lの再生が終わるまでRの再生が始まらなくなってしまうのがまずいです。※

    今、色々考慮した別案作成中ですので、それをupしたらまたご意見いただけますと幸いです。

    ※もしかして私awaitからの処理の流れ勘違いしてるかもしれません…。

    キャンセル

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

  • ただいまの回答率 90.38%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • C#

    9489questions

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

  • Windows Forms

    187questions

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

  • 非同期処理

    145questions

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

  • .NET Framework 4.0

    88questions

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

  • イベントハンドラ

    44questions

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