dポイントプレゼントキャンペーン実施中!

お世話になります。
「C# スレッドから親ウィンドウへの通知の方法は?」というタイトルで、
PostMessageの代替方法について質問させていただいた者です。

その後ウェブにていろいろと調べて見ると、
「C++ の PostMessage、C# では BeginInvoke」のような記述を所々で見かけます。

しかし、どのようにすれば、BeginInvoke を PostMessage の代わりに使えるのか、
よく分かりませんでした。

ご存知の方、具体的な例をご提示願えませんでしょうか。
どうかよろしくお願いします。


以下は自分なりに、こんなカンジ?と作ってみたものです。
これでも子クラスの状態を親ウィンドウへ通知できるのですが...。

namespace DeviceControler
{
  delegate void InformState(int msg);

  public partial class MainForm : Form ---(A)
  {
    InformState InformChildState {get; set;}
    DTLink DtLink {get; set;}

    public MainForm()
    {
      InformChildState = new InformState(ChildStateChanged); ---(B)
      DtLink = null;
    }

    private void MainForm_Load(object sender, EventArgs e)
    {
      DtLink = new DTLink(); ---(C)
      DtLink.InformChildState = InformChildState; ---(D)
      DtLink.Start(); ---(E)
    }

    private void ChildStateChanged(int msg) ---(F)
    {
      
      // (G)から状態値が渡されるので、値に応じて処理を行う
      switch (msg) {
      case ...
      
      }
    }
  }

  class DTLink  ---(G) == (C)の本体
  {
    public InformState InformChildState;

    public void Start(int activeFAPanel = FAPanel.main)
    {
      InformChildState.BeginInvoke(状態値, null, null); ---(H)
    }
  }
}


(A) はウィンドウフォームです
(B) で (F) の delegate を作り、
(C) で 子クラスを作り、
(D)で (C) のクラスに (B) を渡し、
(E) で (C) の中身を起動し(処理は省略です)、
(H) で (F) を呼び出しています。
これにより、(G)の状態を、(A)に通知しています。
(H) はここだけでなく、(G) のクラスの中で様々な処理を行う度に、必要に応じ
て呼び出します。

A 回答 (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変更を閉じ込めてしまうさらっとした書き方をよくしますね。)
    • good
    • 0
この回答へのお礼

toras9000 さん

再度回答をくださってありがとうございます。

書いてくださったコードを真剣に読みました。
大変々々勉強になりました。

昔MFCをかじっていた頃は、並行処理の開始は beginthread、排他はセマフォ、これだけでした。
(単に私が他の手法を知らなかっただけだとは思いますが。)
それに比べると、今はマルチタスクの実現方法が多様で、
また、C#のVer.が上がる毎に新しい機能が追加されたりもするので、
普段から開発業務に携わっているわけではない者にとっては、
たまの機会で目的に応じた手法を選択して学習するのがなかなか大変です。

しかし、前の質問でのBackgroundWorkerもそうなのですが、
こうやって教えていただいて、知らないことを理解するのはとても楽しいことに感じます。

今回もとても参考になりました。
やりたいことが実現できそうです。
どうもありがとうございました。

お礼日時:2014/04/06 15:04

まずこの質問項目に対する回答事項ですが、


「BeginInvoke」メソッドは複数クラスが持っており、どちらも非同期でのメソッド実行を行う目的のものではありますが、デリゲートが持つものとControlクラス(およびその派生クラス。Formクラスなども)が持つものとで意味が異なります。
デリゲートが持つものは単に別スレッドからデリゲートを呼び出すもので、動作としては以下のコードとほとんど同じです。
(BeginInvokeをしたら、EndInvokeで非同期呼び出しの待機&完了を行わなければならないという違いはありますが)

 ThreadPool.QueueUserWorkItem(state => InformChildState(状態));

Controlクラスが持つものは指定した処理がControlを作成したスレッド(通常はメインスレッド)にキューイングされて実行されるということになります。
これはまさしくPostMessageと同じ動作であり、Windowsの場合は内部実装的にもPostMessageを利用しているはずです。
こちらは、主な目的としてはControl派生クラスのUI要素類がスレッドセーフでは無いので、別スレッドからUI更新が必要な場合にUIスレッド側に処理を委譲するために利用します。
定義したメソッドを引数にするほか、ラムダ式を使ってインスタントな処理を書くことも可能です。
# 単純な1本の非同期処理を行う場合であれば、こちらなども参考にどうぞ。
# http://oshiete.goo.ne.jp/qa/8331278.html


ただ、以前に投稿された質問にある複数のスレッドからの通知のようなものの場合、
各スレッドから直接BeginInvokeを利用するというよりは、
前の質問の回答者さんが書かれているBackgroundWorkerの対応がほぼ完璧な回答かと思いますので
その方向の対応方法を学んでいくのが良いかと思います。
# プログラミングのプラットフォームが変われば、手法も変わってきますので、
# 無理にほかのプラットフォームでのやり方を再現するような必要は無いかとおもいます。
    • good
    • 0
この回答へのお礼

詳しい内容をありがとうございます。

マルチスレッドの実現が、昔に比べてムツカシくなったなあ、という感想です。
まあ、正直、昔も「かじった」程度ではあったのですが...。

前に回答いただいた内容を勉強してみます。

ありがとうございました。

お礼日時:2014/03/31 12:45

「C# デリゲート」で検索してください。


例えば
http://ufcpp.net/study/csharp/sp_delegate.html

デリゲートを使って子スレッドから親スレッドにイベントを渡します。
    • good
    • 0
この回答へのお礼

回答をありがとうございます。
 
リンク先拝見しました。
このサイト、別のページですが、時々見てました。
 
経験不足で読解力に自信がないのですが、
なんとなく私の理解(サンプルとして載せたもの)で、
まあまあ合っていそうな...。
(だとすると、それはそれでまた疑問もあるのですが。)
 
もう少し理解に努めます。

ありがとうございました。

お礼日時:2014/03/26 18:32

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!