DOBON.NETプログラミング道掲示板
(現在 過去ログ2 を表示中)

[ 最新記事及び返信フォームをトピックトップへ ]

■33655 / inTopicNo.1)  UIスレッド使用中のBeginInvoke
  
□投稿者/ あばば無人君 一般人(1回)-(2017/08/16(Wed) 10:08:00)
  • アイコン環境/言語:[Windows7(32bit)、C#、.NET Framework-4.0] 
    分類:[.NET] 

    初投稿です。宜しくお願いします。

    ・画面のスレッド(以後「UIスレッド」と記載)以外のスレッドから画面のコントロールにアクセスする場合、フォームなどのBeginInvoke関数で一時的にUIスレッドを使用してアクセスするのが基本かと思います。

    ・私は.NetフォームアプリケーションのUIスレッドは1つだけと認識しています。

    上記の前提で質問なのですが、UIスレッド以外に、たとえば別アプリからパケットを受信したらBeginInvoke関数で画面上のラベルに"受信しました。"というメッセージを表示するスレッド(以後「受信スレッド」と記載)があるフォームアプリケーションがあるとします。

    このアプリケーションでモーダルダイアログを表示中に受信スレッドが何らかのパケットを受信してメッセージを表示しようとしても、UIスレッドが使用中なので受信スレッドはダイアログが閉じられるまで固まったままだろうという認識は間違っているのでしょうか?

    上記の様なアプリケーションのソースを見ながら「こりゃ受信スレッドは固まるかな?」と思い動かしてみたところ、受信スレッドが固まらなかったので不思議です。
    UIスレッド使用中にBeginInvoke関数で画面上のコントロールにアクセスする時は一時的にUIスレッドが2つになるのでしょうか?

    宜しくお願い致します。

    ※お気楽掲示板に投稿していた物をこちらに移しました。(お気楽の方は削除済み)
引用返信 削除キー/
■33656 / inTopicNo.2)  Re[1]: UIスレッド使用中のBeginInvoke
□投稿者/ 魔界の仮面弁士 大御所(1073回)-(2017/08/16(Wed) 11:21:04)
  • アイコンNo33655に返信(あばば無人君さんの記事)
    > ・画面のスレッド(以後「UIスレッド」と記載)以外のスレッドから画面のコントロールにアクセスする場合、
    > フォームなどのBeginInvoke関数で一時的にUIスレッドを使用してアクセスするのが基本かと思います。
    正確には System.Windows.Forms.Control クラスの
    BeginInvoke メソッド / Invoke メソッドですね。
    (このほか、デリゲート等にも BeginInvoke メソッドが存在します)

    また、BackgroundWorker とか SynchronizationContext とか ContinueWith など、
    Invoke / BeginInvoke 以外の手段も用意されています。


    > ・私は.NetフォームアプリケーションのUIスレッドは1つだけと認識しています。

    一つであることが多いですが、一つであるとは限りません。たとえば VB の場合、
    スタートアップ フォーム(My.Forms.Form1 など)が表示される UI スレッドと
    スプラッシュ スクリーン(My.Application.SplashScreen)が表示される UI スレッドは
    異なるスレッドとなります。



    > UIスレッド以外に、たとえば別アプリからパケットを受信したらBeginInvoke関数で画面上のラベルに"受信しました。"というメッセージを表示するスレッド(以後「受信スレッド」と記載)があるフォームアプリケーションがあるとします。
    この場合、受信データを保持しておく変数を、スレッド間で共有しない方が良いでしょうね。
    (変数を共有させると、読み出し中に書き込みが行われないような同期制御も必要になるため)


    > このアプリケーションでモーダルダイアログを表示中に受信スレッドが何らかのパケットを受信してメッセージを表示しようとしても、UIスレッドが使用中なので受信スレッドはダイアログが閉じられるまで固まったままだろうという認識は間違っているのでしょうか?

    モーダルダイアログの表示中だからと言って、UI スレッドが固まることはありません。

    ShowDialog メソッドは同期処理なので、ダイアログが閉じられる(≠破棄される)までの間
    呼び出し元に処理が戻されることはありませんが、その間もメッセージループは回り続けており、
    たとえば PictureBox_Paint や Timer_Tick などは、引き続き UI スレッドのコンテキストで呼び出されます。
引用返信 削除キー/
■33657 / inTopicNo.3)  Re[2]: UIスレッド使用中のBeginInvoke
□投稿者/ あばば無人君 一般人(2回)-(2017/08/16(Wed) 15:30:53)
  • アイコンNo33656に返信(魔界の仮面弁士さんの記事)

    ご回答、ありがとうございます。

    >>このアプリケーションでモーダルダイアログを表示中に受信スレッドが何らかのパケットを受信してメッセージを表示しようとしても、UIスレッドが使用中なので受信スレッドはダイアログが閉じられるまで固まったままだろうという認識は間違っているのでしょうか?
    >
    > モーダルダイアログの表示中だからと言って、UI スレッドが固まることはありません。
    >
    > ShowDialog メソッドは同期処理なので、ダイアログが閉じられる(≠破棄される)までの間
    > 呼び出し元に処理が戻されることはありませんが、その間もメッセージループは回り続けており、
    > たとえば PictureBox_Paint や Timer_Tick などは、引き続き UI スレッドのコンテキストで呼び出されます。

    UIスレッドが固まる事は無いとの事ですが、ご記載の通りモーダルダイアログは閉じられるまで呼出元に処理が戻らない事を考慮すると、UIスレッドの挙動がわからなくなりました。

    前述の通り私は.NetフォームアプリケーションのUIスレッドは基本的に1つの認識ですので、モーダルダイアログ表示中のUIスレッドはユーザーの操作(ダイアログの閉じや廃棄)を待っている状態だと思っておりますが、そもそもそれが間違いなのでしょうか?

    メッセージループが回り続けている==UIスレッドは空いていてコントロールへのアクセスも可能と考えるべきなのでしょうか?

    宜しくお願い致します。

    ※他3点に関する補足、ありがとうございました。

引用返信 削除キー/
■33658 / inTopicNo.4)  Re[3]: UIスレッド使用中のBeginInvoke
□投稿者/ Azulean 大御所(485回)-(2017/08/16(Wed) 16:14:28)
  • アイコン2017/08/16(Wed) 16:16:06 編集(投稿者)

    No33657に返信(あばば無人君さんの記事)
    > メッセージループが回り続けている==UIスレッドは空いていてコントロールへのアクセスも可能と考えるべきなのでしょうか?

    最近は隠蔽されてしまってあまり知られていないかもしれませんが、基本的に Windows アプリケーションは根っこにメッセージループを持っています。
    このメッセージループでマウスクリックなどのウィンドウメッセージを受けて、Form や Control のイベントハンドラを呼び出す流れを作っています。

    このメッセージループで次のウィンドウメッセージを待っている状態を、「UI スレッドが空いている」と表現して良いものです。


    > UIスレッドが固まる事は無いとの事ですが、ご記載の通りモーダルダイアログは閉じられるまで呼出元に処理が戻らない事を考慮すると、UIスレッドの挙動がわからなくなりました。

    ShowDialog によってモーダルダイアログを表示している場合、その ShowDialog の中でメッセージループが回っていると考えてください。
    (外側のメッセージループのイベントを受けて、ShowDialog した結果、内側でメッセージループを回すので、ループの中でループを回しているような状態)

    > 前述の通り私は.NetフォームアプリケーションのUIスレッドは基本的に1つの認識ですので、モーダルダイアログ表示中のUIスレッドはユーザーの操作(ダイアログの閉じや廃棄)を待っている状態だと思っておりますが、そもそもそれが間違いなのでしょうか?

    前述のように、モーダルダイアログを表示中も中でメッセージループが回るので、「間違い」と言えるでしょうね。

    仮に あばば無人君さん が言われるようにメッセージループではなく、ずっと待っている状態だと、Windows アプリケーションとしては「応答なし」となってしまい、クリックなどに反応できない仕様となっています。
引用返信 削除キー/
■33659 / inTopicNo.5)  Re[3]: UIスレッド使用中のBeginInvoke
□投稿者/ 魔界の仮面弁士 大御所(1074回)-(2017/08/16(Wed) 17:07:15)
  • アイコン2017/08/17(Thu) 09:22:38 編集(投稿者)

    No33657に返信(あばば無人君さんの記事)
    > ご記載の通りモーダルダイアログは閉じられるまで呼出元に処理が戻らない事を考慮すると、UIスレッドの挙動がわからなくなりました。

    たとえば下記の処理を見てください。
    FontDialog.ShowDialog によってモーダルダイアログを表示しています。


    private void button1_Click(object sender, EventArgs e)
    {
     using (var dlg = new FontDialog())
     {
      dlg.ShowApply = true;
      dlg.ShowColor = true;
      dlg.Color = label1.ForeColor;
      dlg.Font = label1.Font;
      dlg.Apply += delegate { SetLabelConfig(dlg.Font, dlg.Color); };
      if (dlg.ShowDialog() == DialogResult.OK)
      {
       SetLabelConfig(dlg.Font, dlg.Color);
      }
     }
    }

    void SetLabelConfig(Font newFont, Color newColor)
    {
     label1.Text = newFont.Name;
     label1.Font = newFont;
     label1.ForeColor = newColor;
    }


    このとき、ダイアログの[適用]ボタンが押されると、Apply イベントが呼び出されます。

    ダイアログが開いている間(ShowDialog から DialogResult が返される前)であっても、
    Apply イベントを通じて、UI スレッドのコンテキストで SetLabelConfig メソッドが
    処理されることになります。


    ちなみに、Application.Run が呼ばれると、その中でメッセージループが
    回されるようになっていますが、他にも ShowDialog メソッドや DoEvents メソッドが
    メッセージループを走らせるようになっています。

    http://bit.ly/2wPtNkA



    > 前述の通り私は.NetフォームアプリケーションのUIスレッドは基本的に1つの認識ですので、

    その認識で良いと思います。
    先の VB のような例外パターンもあるので、あくまでも「基本的には」ですが。



    > モーダルダイアログ表示中のUIスレッドはユーザーの操作(ダイアログの閉じや廃棄)を

    ユーザーの操作には、キーボード操作やマウス入力などがありますが、
    前回の回答でも述べた、「画面の再描画」や「タイマー通知」も含まれます。
    いずれもウィンドウメッセージによるものですね。

    今回のサンプルで言えば、『if (dlg.ShowDialog() == DialogResult.OK)』によって
    待機状態になるのは「Form1 上の button1_Click イベントハンドラ」だということです。

    モーダルダイアログの表示中は、呼び出し元の親画面(Form1)をユーザーが操作することは
    できませんが、ダイアログ表示中も、Form1 の Paint、FontDialog の Apply、
    Timer の Tick などのイベントは同じスレッド上で呼ばれますので、
    そうしたタイミングで「this.Left += 50;」などを呼び出せば、モーダルダイアログの
    表示中であっても、呼び出し元(Form1)のウィンドウの位置を変更することができます。


    > 待っている状態だと思っておりますが、

    待っているというよりは、何もしていない状態、の方が近いかも。

    いわゆるビジー状態(ループ処理や計算など、長い処理が行われている最中)においては、
    ウィンドウメッセージは処理されず、イベントも処理されません。再描画すらされません。

    しかし、何も処理されていない状態であれば、ウィンドウ メッセージを
    受け付けることができ、それが Control / Form によって、イベント等として
    処理されたり、画面に反映されたりします。


    > メッセージループが回り続けている==UIスレッドは空いていてコントロールへのアクセスも可能と考えるべきなのでしょうか?

    ワーカースレッドが無く、単一の UI スレッドのアプリであっても、ビジー状態の時に
    何か操作しようとすれば、処理が直ちに呼ばれず待たされたりします。
    (処理中の要求が後回しにされるのか間引かれるのかは、メッセージによって異なります)
引用返信 削除キー/
■33660 / inTopicNo.6)  Re[4]: UIスレッド使用中のBeginInvoke
□投稿者/ あばば無人君 一般人(3回)-(2017/08/17(Thu) 09:24:51)
  • アイコンAzuleanさん、魔界の仮面弁士さん、返信が遅くなり失礼致しました。
    お二人の投稿を読んで、自分のどこがいけなかったかようやく気付けました。

    モーダルダイアログを開いている時の状態と、ビジー状態(時間の掛かる処理)を、「同じ物」と認識していたのが最大の過ちでした。

    そこに気付いたら、モーダルダイアログが開いている状態でもメッセージループは動いていてUIへのアクセス(描画)が可能である事を違和感なく受け入れられました。

    最初の投稿で記載していた、モーダルダイアログ開き中に別スレッドからBeginInvokeを介してUIにアクセスできるのも納得できました。
    (UIスレッドは「空いている」という事ですからね)

    結局、当投稿のタイトル「UIスレッド使用中の」は、そもそも「使ってない」だろ!って話でした。恥ずかしい限りです。

    Azuleanさん
    > ShowDialog によってモーダルダイアログを表示している場合、その ShowDialog の中でメッセージループが回っていると考えてください。
    私が納得できるような考え方を示して頂きありがとうございました。

    魔界の仮面弁士さん
    具体的なサンプルや、
    > いわゆるビジー状態(ループ処理や計算など、長い処理が行われている最中)においては、
    > ウィンドウメッセージは処理されず、イベントも処理されません。再描画すらされません。
    >
    > しかし、何も処理されていない状態であれば、ウィンドウ メッセージを受け付けることができ、
    > それが Control / Form によって、イベント等として処理されたり、画面に反映されたりします。
    といった記載、目からうろこでした。ありがとうございました。

    自分の勘違いに気付けてすっきりしました。
    お二人とも、本当にありがとうございました。

解決み!
引用返信 削除キー/



トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

Mode/  Pass/


- Child Tree -