C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[2]: serialPortの「有効ではないスレッド間の操作」エラー


(過去ログ 59 を表示中)

[トピック内 4 記事 (1 - 4 表示)]  << 0 >>

■34205 / inTopicNo.1)  serialPortの「有効ではないスレッド間の操作」エラー
  
□投稿者/ am0123 (1回)-(2009/03/23(Mon) 01:20:12)

分類:[C#] 

2009/03/24(Tue) 02:11:12 編集(投稿者)
いつも参考にさせて頂いております。
大変困り果てまして、どなたかにお知恵をお貸しいただきたく投稿します。


下記ソースコードは、
Form1に、serialPort1を持つユーザーコントロールを貼って、受信内容を
イベントで通知するプログラムです。


@位置の、受信データをtextBox1に表示する処理で、

{"有効ではないスレッド間の操作: コントロールが作成されたスレッド以外
のスレッドからコントロール 'textBox1' がアクセスされました。"}

が発生します。


DelegateとInvokeの話だとは思ったのですが、ユーザーコントロールに Timer
を貼ってそのタイムアップから、OnCommReadを発行したところ正常に動作します。


お教え頂きたいのは、
コンテナのイベント処理(userControl11_CommRead)で、Invoke不要にする
方法です。

イベントを発行させた後なのでユーザーコントロールとは別のスレッドだと思う
のですが、コンテナとも違うスレッドだと言うことですので、どうしたらよい
でしょうか。

変な思い違いがあるのかもしれませんが、どうぞよろしくお願いいたします。



** userControl11(ユーザーコントロール)**

public delegate void CommReadHandler(string res);
public partial class UserControl1 : UserControl
{
    public event CommReadHandler CommRead;
    protected virtual void OnCommRead( string res)
    {
        CommReadHandler handler = CommRead;
        handler(res);
    }

    private void serialPort1_DataReceived(object sender, 
                                          SerialDataReceivedEventArgs e)
    {
        byte[] tempBuf = new byte[1024];
        int tempBufCount = serialPort1.BytesToRead;
        serialPort1.Read( tempBuf, 0, tempBufCount);

        OnCommRead( Encoding.ASCII.GetString(tempBuf, 0, tempBufCount));
    }
}

** Form1(コンテナ) **
private void Form1_Load(object sender, EventArgs e)
{
    this.userControl11.CommRead += 
        new EventSample.CommReadHandler(this.userControl11_CommRead);
}

private void userControl11_CommRead(string res)
{
    textBox1.Text = res;        // @ ここでエラーが発生します。
}


** 補足
当然ですが、serialPortの場合はInvokeRequired がtrueです。(@の位置)


引用返信 編集キー/
■34207 / inTopicNo.2)  Re[1]: serialPortの「有効ではないスレッド間の操作」エラー
□投稿者/ Azulean (333回)-(2009/03/23(Mon) 07:10:21)
> イベントを発行させて後なのでユーザーコントロールとは別のスレッドだと思う
> のですが、コンテナとも違うスレッドだと言うことですので、どうしたらよい
> でしょうか。
違います。
イベント(OnCommRead)を呼び出したスレッドと同じスレッドで動きます。

フォームもユーザコントロールも現状、メインスレッドに属しています。
serialPort1_DataReceivedが別スレッドか実行されることで、OnCommReadもそのスレッド(メインスレッドではない)で呼び出され、スレッドが異なる状態でtextBox1.textを操作しているので、例外がスローされます。

> お教え頂きたいのは、
> コンテナのイベント処理(userControl11_CommRead)で、Invoke不要にする
> 方法です。
serialPort1_DataReceivedではInvokeRequiredがtrueなのですよね?
なら、OnCommReadを呼ぶ前に、Invokeすれば良いのでは?
(OnCommReadの呼び出し自体をInvokeする)

this.Invoke(OnCommRead, Encoding.ASCII.GetString(tempBuf, 0, tempBufCount));


なお、参考として書いておきますが、イベントとして提供するなら、基本的に(object sender, EventArgsの派生クラス e)という引数にすることがデザインガイドで述べられています。
http://msdn.microsoft.com/ja-jp/library/ms229011.aspx
引用返信 編集キー/
■34208 / inTopicNo.3)  Re[1]: serialPortの「有効ではないスレッド間の操作」エラー
□投稿者/ よねKEN (297回)-(2009/03/23(Mon) 07:12:03)
> protected virtual void OnCommEvent( string res)
> {
> CommReadHandler handler = CommRead;
> handler(res);
> }

ここのhanlderの呼び出し部分か、

> private void serialPort1_DataReceived(object sender,
> SerialDataReceivedEventArgs e)
> {
> byte[] tempBuf = new byte[1024];
> int tempBufCount = serialPort1.BytesToRead;
> serialPort1.Read( tempBuf, 0, tempBufCount);
>
> OnCommRead( Encoding.ASCII.GetString(tempBuf, 0, tempBufCount));
> }
> }

ここのOnCommReadの呼び出しをControl.Invokeメソッドを使って呼び出すようにすれば、
userControl11_CommRead側は特に意識する必要はないのではないでしょうか。

#蛇足ですが、handler(res)の直前にhandlerのnullチェックも必要ですね。
引用返信 編集キー/
■34237 / inTopicNo.4)  Re[2]: serialPortの「有効ではないスレッド間の操作」エラー
□投稿者/ am0123 (2回)-(2009/03/24(Tue) 02:16:34)
Azulean さん
よねKEN さん
(まとめて返信、失礼します。お名前は返信順。)

イベントの引数のガイドラインついて、ご指摘の通り修正しようと思います。
handler(res) 直前のnullチェックについて、ご指摘の通り修正しようと思います。

this.Invoke(OnCommRead, Encoding.ASCII.GetString(tempBuf, 0, tempBufCount));
ですんなり動作しました。
this.Invoke(handler, s); も、うまくいきましたので、こちらでやろうと思います。

(1)「イベントを介せば、スレッドセーフ」だと思っていた
(2)this.InvokeとCommRead.Invokeの違いを理解できていない
2点が致命的でした。

(1)については、処理をForm側に任せているのに、なぜInvokeが必要なのか今ひとつ釈然としませんが、もう少し勉強しようと思います。
(2)については、ただの勉強不足と言うしかないので、これから調べます。


今回の質問に関してはとても満足のいく結果になりました。

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


* 質問の誤記をなおしました。
X protected virtual void OnCommEvent( string res)
O protected virtual void OnCommRead( string res)

X new EventSample.AlarmEventHandler(this.userControl11_CommRead);
O new EventSample.CommReadHandler(this.userControl11_CommRead);

解決済み
引用返信 編集キー/


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

このトピックに書きこむ

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

管理者用

- Child Tree -