- ベストアンサー
C# スレッドから親ウィンドウへの通知の方法は?
- C#のスレッドから親ウィンドウへの通知方法について知りたいです。
- PostMessageの代替方法としてBeginInvokeを使用する方法について詳しく教えてください。
- 具体的な例を交えて、BeginInvokeを使用した子クラスの状態を親ウィンドウへ通知する方法について教えてください。
- みんなの回答 (3)
- 専門家の回答
質問者が選んだベストアンサー
- ベストアンサー
昔に比べてムツカシとのことですが、、、、、 自分もMFCを利用していた時もありましたし、フレームワークを使わずにネイティブなC言語/Win32API環境でコードを書いていた時もありましたが、それらに比べてC#でのマルチスレッドの実現はかなり簡単になっていると思っています。 きっちりしたアプリを作る場合には、だいたいは非同期処理の部分に進捗表示と処理のキャンセルのUIを持たせる必要が出てくると思います。(PostMessageによるメッセージング機構を引き合いに出されていたのもそういう関連からかと思います) No.2の回答でBackgroundWorkerを推したのは、MSDNのリファレンスページでも書かれていますがBackgroundWorkerがUI連携を前提とした設計のクラスですので、そこから入るのが良いかと思ってのことです。 話が広がり過ぎるかと思い書きませんでしたが、C#で非同期処理を実現する方法は多数の選択肢があって、シンプルに作るならばたとえば以下のような形もあります。これは結構書きやすいと思います。 (1つのスレッドだけの例ですが、複数あっても大したことは無いと思います。) PostMessageは通知とUIスレッド側へのコンテキストスイッチの2つの機能を備えたものと言えると思いますが、別に1操作でそれを行えないと実現できないことがあると言うわけでもありませんので、スレッドではイベントを単に通知して、利用側(UI操作が必要な側)が自分の都合でUIにコンテキストを移すという感じです。 こう分離しておくと、非同期処理側がUIを全く意識しなくてよくて、UIフレームワークを別のものに変えたとしても非同期処理に変更の必要がないというような利点もありますね。 class AsyncWorker { public event Action Started; public event Action Finished; public void Start() { ThreadPool.QueueUserWorkItem(_ => run()); } private void run() { this.Started(); // 何かの処理 this.Finished(); } } private void button_click() { // 非同期処理クラスをのイベントをハンドルして非同期処理開始。 // 利用側でUIへ処理委譲 var asyncWork = new AsyncWorker(); asyncWork.Started += () => this.BeginInvoke(new MethodInvoker(beginAsync)); asyncWork.Finished += () => this.BeginInvoke(new MethodInvoker(beginAsync)); asyncWork.Start(); } private void beginAsync() { // 開始時UI変更など } private void endAsync() { // 終了時UI変更など } 非同期処理の終了待機については、(ここでは途中の進捗通知やキャンセルサポートなをとりあえず置いておくとして)以下のようにすることも出来ますね。 private void Start() { ThreadPool.QueueUserWorkItem(_ => { // 複数の非同期処理を開始して全部が終了するのを待つ var exitEvents = Enumerable.Range(0, 3).Select(n => new ManualResetEvent(false)).ToArray(); foreach (var evt in exitEvents) { ThreadPool.QueueUserWorkItem(__ => { // なんらかの非同期処理 // 終了フラグのイベントをシグナル化 evt.Set(); }); } WaitHandle.WaitAll(exitEvents); this.BeginInvoke(new MethodInvoker(onEnd)); }); } private void onEnd() { // 全部終わった後の処理 } もっとも、これはC#としては少々古い書き方で、現在であれば並列処理ライブラリなどが充実しているのでたとえば上記の複数実行&待機と同じことが以下で出来ますが。 Parallel.For(0, 2, (num) => { // なんらかの非同期処理 }); 他にも一部のデータ処理の一部だけ並列化したい場合はPLINQで以下のような形とか。 // URLのリストがあるとして var urlList = new List<Uri>(); // 同時に最大10個までを非同期ダウンロードして var downloadData = urlList.AsParallel() .WithDegreeOfParallelism(10) .Select(url => { using (var webClient = new WebClient()) { return webClient.DownloadString(url); } }); // ダウンロード出来たものから何らかの処理をする、など。 foreach (var data in downloadData) { // データを処理 } 手段はいろいろありますので順に全てを学んで、C#のガベージコレクションやラムダ式によるクロージャなんかは非同期処理とも相性が良いので、そのあたりも組み合わせて必要な所に必要なものを利用するようにすれば、結構簡単に目的を実現できたりすると思います。 (私のの場合は、簡単なツールを作るぐらいの場合であればNo.2の回答に示したURL先にも書いたように、1メソッド内で開始のUI変更と終了のUI変更を閉じ込めてしまうさらっとした書き方をよくしますね。)
その他の回答 (2)
まずこの質問項目に対する回答事項ですが、 「BeginInvoke」メソッドは複数クラスが持っており、どちらも非同期でのメソッド実行を行う目的のものではありますが、デリゲートが持つものとControlクラス(およびその派生クラス。Formクラスなども)が持つものとで意味が異なります。 デリゲートが持つものは単に別スレッドからデリゲートを呼び出すもので、動作としては以下のコードとほとんど同じです。 (BeginInvokeをしたら、EndInvokeで非同期呼び出しの待機&完了を行わなければならないという違いはありますが) ThreadPool.QueueUserWorkItem(state => InformChildState(状態)); Controlクラスが持つものは指定した処理がControlを作成したスレッド(通常はメインスレッド)にキューイングされて実行されるということになります。 これはまさしくPostMessageと同じ動作であり、Windowsの場合は内部実装的にもPostMessageを利用しているはずです。 こちらは、主な目的としてはControl派生クラスのUI要素類がスレッドセーフでは無いので、別スレッドからUI更新が必要な場合にUIスレッド側に処理を委譲するために利用します。 定義したメソッドを引数にするほか、ラムダ式を使ってインスタントな処理を書くことも可能です。 # 単純な1本の非同期処理を行う場合であれば、こちらなども参考にどうぞ。 # http://okwave.jp/qa/q8331278.html ただ、以前に投稿された質問にある複数のスレッドからの通知のようなものの場合、 各スレッドから直接BeginInvokeを利用するというよりは、 前の質問の回答者さんが書かれているBackgroundWorkerの対応がほぼ完璧な回答かと思いますので その方向の対応方法を学んでいくのが良いかと思います。 # プログラミングのプラットフォームが変われば、手法も変わってきますので、 # 無理にほかのプラットフォームでのやり方を再現するような必要は無いかとおもいます。
お礼
詳しい内容をありがとうございます。 マルチスレッドの実現が、昔に比べてムツカシくなったなあ、という感想です。 まあ、正直、昔も「かじった」程度ではあったのですが...。 前に回答いただいた内容を勉強してみます。 ありがとうございました。
- Hayashi_Trek
- ベストアンサー率44% (366/818)
「C# デリゲート」で検索してください。 例えば http://ufcpp.net/study/csharp/sp_delegate.html デリゲートを使って子スレッドから親スレッドにイベントを渡します。
お礼
回答をありがとうございます。 リンク先拝見しました。 このサイト、別のページですが、時々見てました。 経験不足で読解力に自信がないのですが、 なんとなく私の理解(サンプルとして載せたもの)で、 まあまあ合っていそうな...。 (だとすると、それはそれでまた疑問もあるのですが。) もう少し理解に努めます。 ありがとうございました。
お礼
toras9000 さん 再度回答をくださってありがとうございます。 書いてくださったコードを真剣に読みました。 大変々々勉強になりました。 昔MFCをかじっていた頃は、並行処理の開始は beginthread、排他はセマフォ、これだけでした。 (単に私が他の手法を知らなかっただけだとは思いますが。) それに比べると、今はマルチタスクの実現方法が多様で、 また、C#のVer.が上がる毎に新しい機能が追加されたりもするので、 普段から開発業務に携わっているわけではない者にとっては、 たまの機会で目的に応じた手法を選択して学習するのがなかなか大変です。 しかし、前の質問でのBackgroundWorkerもそうなのですが、 こうやって教えていただいて、知らないことを理解するのはとても楽しいことに感じます。 今回もとても参考になりました。 やりたいことが実現できそうです。 どうもありがとうございました。