140

UARTを使用してマイクロコントローラから温度値をC#インターフェイスに送信し、温度を表示したいLabel.Content。これが私のマイクロコントローラのコードです。

while(1) {
   key_scan(); // get value of temp
   if (Usart_Data_Ready())
   {
      while(temperature[i]!=0)
      {
         if(temperature[i]!=' ')
         {
            Usart_Write(temperature[i]);
            Delay_ms(1000);
         }
         i = i + 1;
      }
      i =0;
      Delay_ms(2000);
   }
}

そして私のC#コードは:

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
   txt += serialPort1.ReadExisting().ToString();
   textBox1.Text = txt.ToString();
}

しかし、例外が発生します」クロススレッド操作が無効です。コントロール 'textBox1'が、それが作成されたスレッド以外のスレッドからアクセスしました。「 マイクロコントローラから温度文字列を取得してこのエラーを削除する方法を教えてください。


  • 別のスレッドを介してUI(メインスレッド)を変更しようとしています。 - Evan Mulawski

6 답변


254

に受信したデータserialPort1_DataReceivedmethodはUIスレッド以外のスレッドコンテキストから来ているため、このエラーが発生します。

これを解決するには、MSDNの記事で説明されているように、ディスパッチャを使用する必要があります。

方法:Windowsフォームコントロールへのスレッドセーフな呼び出しを行う

テキストプロパティを直接設定する代わりにserialport1_DataReceivedメソッド、このパターンを使用します。

delegate void SetTextCallback(string text);

private void SetText(string text)
{
  // InvokeRequired required compares the thread ID of the
  // calling thread to the thread ID of the creating thread.
  // If these threads are different, it returns true.
  if (this.textBox1.InvokeRequired)
  { 
    SetTextCallback d = new SetTextCallback(SetText);
    this.Invoke(d, new object[] { text });
  }
  else
  {
    this.textBox1.Text = text;
  }
}

だからあなたの場合:

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
  txt += serialPort1.ReadExisting().ToString();
  SetText(txt.ToString());
}


  • コントロールにInvokeRequiredプロパティがないことに問題がある場合は、親フォームのInvokeRequiredプロパティを試してください。つかいますif (this.InvokeRequired) { //SetTextCallBack etc. }の代わりにif (this.textBox1.InvokeRequired) { //SetTextCallBack etc. } - Jroonk
  • 意志control.BeginInvoke仕事も?解決策は、このように1行にすることもできますね。textbox1.BeginInvoke((MethodInvoker)delegate(){ textbox1.Text = txt.ToString(); }); - newbieguy
  • 他の誰かがこれを見逃している場合(私のように、誰よりもFuncやlambdasに慣れている人)、SetTextCallback呼び出すために働くSetTextあなたが渡すことですSetTextnew SetTextCallback()。 DUHHH - ErikE

42

これで十分かどうかはわかりませんが、静的なThreadHelperClassクラスを作成し、次のように実装しました。これで、コーディングなしでさまざまなコントロールのtextプロパティを簡単に設定できます。

public static class ThreadHelperClass
    {
        delegate void SetTextCallback(Form f, Control ctrl, string text);
        /// <summary>
        /// Set text property of various controls
        /// </summary>
        /// <param name="form">The calling form</param>
        /// <param name="ctrl"></param>
        /// <param name="text"></param>
        public static void SetText(Form form, Control ctrl, string text)
        {
            // InvokeRequired required compares the thread ID of the 
            // calling thread to the thread ID of the creating thread. 
            // If these threads are different, it returns true. 
            if (ctrl.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                form.Invoke(d, new object[] { form, ctrl, text });
            }
            else
            {
                ctrl.Text = text;
            }
        }
    }

コードを使う:

 private void btnTestThread_Click(object sender, EventArgs e)
        {
            Thread demoThread =
               new Thread(new ThreadStart(this.ThreadProcSafe));
            demoThread.Start();
        }

        // This method is executed on the worker thread and makes 
        // a thread-safe call on the TextBox control. 
        private void ThreadProcSafe()
        {
            ThreadHelperClass.SetText(this, textBox1, "This text was set safely.");
            ThreadHelperClass.SetText(this, textBox2, "another text was set safely.");
        }


  • That's awesome solution、最も素晴らしいことは"it's open for extension and generic"。あなたが望むようにあなたは単に新しいUI更新機能を追加することができます、ありがとう - Basheer AL-MOMANI
  • 素晴らしいもの!そして、代わりにテキストを読む必要がある場合は、次のようにします。デリゲートストリングGetTextCallback(Form f、Control ctrl); public static string GetText(フォームフォーム、Control ctrl){文字列テキスト; if(ctrl.InvokeRequired){GetTextCallback d = new GetTextCallback(GetText); text =(文字列)(form.Invoke(d、new object [] {form、ctrl})); } else {text = ctrl.Text;テキストを返します。 }} - hypers
  • ThreadProcSafeでカスタムテキストを使用する方法の説明を誰かにもらえますか。 Eliseoの提案も試したが、うまくいかなかった。 - Pablo Costa

24

あなたは単にこれをすることができます。

TextBox.CheckForIllegalCrossThreadCalls = false;


  • 'リリースを実行するとエラーが発生するため、悪い考えです。コンパイル。 - omglolbah
  • しかし、マルチスレッドプログラミングをトレーニングするための良い考え - Mehdi Khademloo
  • .NET 2.の頃には明らかに機能していた古いサンプルコードをいくつか入手しました。コードの他の側面を分析するだけなので、この回答は私が行っていることには最適です。 - Dave
  • @DerfSkren詳細な説明をお願いします。^ - Eric Wu
  • そのフラグを設定する@EricWuは、デバッグビルドでもリリースビルドでも同じ効果があります。 「問題」実際に安全なものについて警告を受けたため、作成したすべてのGUIでホイールを再発明することを余儀なくされたため、戻ってくることはできません。 - Derf Skren

20

次の拡張子を使用して、単に次のようなアクションを渡します。

_frmx.PerformSafely(() => _frmx.Show());
_frmx.PerformSafely(() => _frmx.Location = new Point(x,y));

拡張クラス:

public static class CrossThreadExtensions
{
    public static void PerformSafely(this Control target, Action action)
    {
        if (target.InvokeRequired)
        {
            target.Invoke(action);
        }
        else
        {
            action();
        }
    }

    public static void PerformSafely<T1>(this Control target, Action<T1> action,T1 parameter)
    {
        if (target.InvokeRequired)
        {
            target.Invoke(action, parameter);
        }
        else
        {
            action(parameter);
        }
    }

    public static void PerformSafely<T1,T2>(this Control target, Action<T1,T2> action, T1 p1,T2 p2)
    {
        if (target.InvokeRequired)
        {
            target.Invoke(action, p1,p2);
        }
        else
        {
            action(p1,p2);
        }
    }
}


  • これはうまくいった! 1つ注意点として、target.Invoke呼び出しをtarget.BeginInvokeに置き換えました。私はタスクがハングアップすることでいくつかの問題を抱えていました、そしてこれはそれらの1つを解決しました。 - Edyn

10

前の答えと同じように、 しかし、スレッド間の呼び出し例外を発生させることなく、すべてのControlプロパティを使用できるようにする非常に短い追加機能です。

ヘルパーメソッド

    /// <summary>
    /// Helper method to determin if invoke required, if so will rerun method on correct thread.
    /// if not do nothing.
    /// </summary>
    /// <param name="c">Control that might require invoking</param>
    /// <param name="a">action to preform on control thread if so.</param>
    /// <returns>true if invoke required</returns>
    public bool ControlInvokeRequired(Control c,Action a)
    {
        if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate { a(); }));
        else return false;

        return true;
    }

使用例

    // usage on textbox
    public void UpdateTextBox1(String text)
    {
        //Check if invoke requied if so return - as i will be recalled in correct thread
        if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
        textBox1.Text = ellapsed;
    }

    //Or any control
    public void UpdateControl(Color c,String s)
    {
        //Check if invoke requied if so return - as i will be recalled in correct thread
        if (ControlInvokeRequired(myControl, () => UpdateControl(c,s))) return;
        myControl.Text = s;
        myControl.BackColor = c;
    }


6

共有コンテナーを使用して、スレッド間でデータを転送します。

リンクされた質問


関連する質問

最近の質問