2008年4月22日火曜日

イベントから GUI をアクセスすると、.NET コンポーネントがハングする

http://www.nsoftware.com/kb/showentry.aspx?entry=12060401

該当製品:


  • IP*Works!
  • IP*Works! SSL


現象:


非同期コンポーネントのイベント内から GUI をアクセスすると、コンポーネントがハングしてしまいます。

解決法:


.NET では、GUI コンポーネントは GUI スレッドからアクセスされなければなりません。非同期コンポーネントのイベント内から GUI を更新しようとすると、アプリケーションはハングアップしてしまう可能性があります。

IP*Works! コンポーネントはスレッド動作の観点からは2つのカテゴリーに分類できます。これは、効率や実装されるプロトコルの性格から決まります。Ipport、Udpport、Icmpport、Mcast、Xmpp、Telnet、Mx、Dns は非ブロックかつ非同期なコンポーネントであり、これ以外はブロックかつ同期的なコンポーネントです。

ブロック/同期コンポーネントは、GUI アプリケーション内でさえそれらを動作させるための特別な手順を実装する必要が全く無いという意味では「プラグ&プレイ」です。

一方非ブロック/非同期コンポーネントは、GUI アプリケーション内で使用される場合には「GUI コンポーネントは GUI スレッドからのみアクセスされなければならない」という .NET の制限のため特別な手順を要します。

非ブロック非同期なやり方で接続やデータ送信および切断を行うには、それぞれ Connect、Send および Disconnect を使用します。

そしてそれらの操作結果を後ほど通知するイベント Connected、DataIn および Disconnected がそれぞれ用意されています。各メソッドの呼び出しは、コンポーネントに処理を実行するよう「依頼」するだけであり制御は直ちに呼び出し側スレッドに戻されますが、それらに対応するイベントは当該コンポーネントの内部のワーカスレッドから生成され発生します。従って、このイベント内から GUI コンポーネントの更新を行おうとすると、アプリケーションは恐らくハングアップしてしまうことでしょう。

.NET Edition の V6 (【訳注】V8 も) ではコンポーネントの InvokeThrough プロパティに当該フォームのインスタンスを設定することで、コンポーネントに対処し、メインスレッド上で自動的に実行するようになります。
    xmpp1.InvokeThrough = this;

別の方法としては、InvokeThrough を既定値 (null) のままにしておき、イベントから GUI フォームをアクセスしないようにする、あるいはあなた自身が invoke 処理を記述すのいずれかを行うことも可能です。

--

Compact Framework 開発においては、事態は若干複雑です。.NET CF Edition には InvokeThrough プロパティが無いため、この問題はあなた自身が処理しなくてはなりません。(【訳注】初期の V8 には InvokeThrough は実装されていませんでしたが、その後 V8 では .NET CF でも InvokeThrough をご利用頂けるようになっています。) 以下は IPPort コンポーネントを使って .NET CF でどのように invoke を扱うかを示す C# コードです:
    nsoftware.IPWorks.IpportConnectedEventArgs e; 


private void ipport1_OnConnected(object sender,nsoftware.IPWorks.IpportConnectedEventArgs e)
{
ipport1.DataToSend = "Thanks for connecting!";
this.e = e;
this.Invoke(new EventHandler(MyClientConnected));
}


void MyClientConnected(object sender, EventArgs e)
{
tbLog.Text = "Connected to remote host: " + this.e.Description;
}

---
VB.NET では、例として XMPP コンポーネントを使ってみましょう。
  1. まずイベント引数を保持するオブジェクトを生成します。
        Private e As nsoftware.IPWorks.XmppMessageInEventArgs
  2. 次にあなたの GUI コードを実行する delegate 関数を記述します。(ここでは MyXmppMessageIn と名前をつけましたが、好きな名前を使って構いません。)
        Delegate Sub MyEventHandler(ByVal sender As Object, ByVal e As nsoftware.IPWorks.XmppMessageInEventArgs)
    Private MyXmppMessageInD As MyEventHandler

    ...
    MyXmppMessageInD = New MyEventHandler(AddressOf MyXmppMessageIn)
    ...

    Sub MyXmppMessageIn(ByVal sender As Object, ByVal e As EventArgs)
    'My gui code, use Me.e for event args
    End Sub
  3. そして IP*Works! のイベントそのものから MyXmppMessageIn を invoke します:
        Private Sub xmpp1_OnMessageIn(ByVal sender As Object, ByVal e As nsoftware.IPWorks.XmppMessageInEventArgs) Handles Xmpp1.OnMessageIn 
    Invoke(MyXmppMessageInD)
    End Sub

0 件のコメント: