クロススレッド操作が無効です:作成されたスレッド以外のスレッドからアクセスされたコントロール


584

シナリオがあります。(Windowsフォーム、C#、. NET)

  1. いくつかのユーザーコントロールをホストするメインフォームがあります。
  2. ユーザーコントロールが重いデータ操作を行うため、UserControl_Loadメソッドを直接呼び出すと、ロードメソッドの実行中にUIが応答しなくなります。
  3. これを克服するために、別のスレッドでデータをロードします(できる限り既存のコードを変更しないようにします)。
  4. 私は、データをロードするバックグラウンドワーカースレッドを使用しました。完了すると、アプリケーションに作業が完了したことを通知します。
  5. 今、本当の問題が起こりました。すべてのUI(メインフォームとその子ユーザーコントロール)は、プライマリメインスレッドで作成されました。usercontrolのLOADメソッドでは、userControlのコントロール(テキストボックスなど)の値に基づいてデータをフェッチしています。

擬似コードは次のようになります。

コード1

UserContrl1_LoadDataMethod()
{
    if (textbox1.text == "MyName") // This gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

それが与えた例外は

クロススレッド操作が無効です:作成されたスレッド以外のスレッドからアクセスされたコントロール。

これについてもっと知るために私はいくつかグーグルを行い、次のコードを使用するような提案が出ました

コード2

UserContrl1_LoadDataMethod()
{
    if (InvokeRequired) // Line #1
    {
        this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
        return;
    }

    if (textbox1.text == "MyName") // Now it wont give an exception
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

BUT BUT BUT ...正直に戻ってきたようです。アプリケーションは再び応答しなくなります。if条件の1行目が実行されたことが原因と思われます。ロードタスクは、親スレッドによって再度実行され、私が生成した3番目のタスクではありません。

私がこれを正しいと思ったのか、間違っていたのかわかりません。私はスレッディングが初めてです。

これをどのように解決しますか?また、Line#1 ifブロックの実行の効果は何ですか?

状況は次のとおりです。コントロールの値に基づいて、データをグローバル変数にロードします。子スレッドからコントロールの値を変更したくありません。子スレッドからこれを行うつもりはありません。

したがって、値にアクセスするだけで、対応するデータをデータベースからフェッチできます。


このエラーの特定のインスタンスについて、回避策はフォームのBackgroundWorkerを使用してコードのデータ集約型の部分を処理することであることがわかりました。(すなわちbackgroundWorker1_DoWork()メソッドに問題のあるコードのすべてを入れて、backgroundWorker1.RunWorkerAsync()を介してそれを呼び出す)...これら二つのソースが正しい方向に私を指摘:stackoverflow.com/questions/4806742/... youtube.com/ watch?v = MLrrbG6V1zM
ジョリア

回答:


433

あたりとしてPrera​​k Kの更新コメント(削除されたため):

私は質問を適切に提示しなかったと思います。

状況は次のとおりです。コントロールの値に基づいて、データをグローバル変数にロードします。子スレッドからコントロールの値を変更したくありません。子スレッドからこれを行うつもりはありません。

したがって、値にアクセスするだけで、対応するデータをデータベースからフェッチできます。

必要なソリューションは次のようになります。

UserContrl1_LOadDataMethod()
{
    string name = "";
    if(textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if(name == "MyName")
    {
        // do whatever
    }
}

コントロールのスレッドに戻るに、別のスレッドで本格的な処理を行ってください。例えば:

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}

1
C#プログラミングを行ってからしばらく経ちましたが、MSDNの記事とパッチの知識に基づいて、そのように見えます。
ジェフハバード2016

1
違いは、Invoke()が同期的に実行されている間、BeginInvoke()は非同期です。stackoverflow.com/questions/229554/...
frzsombor

178

UIのスレッドモデル

基本的な概念を理解するには、UIアプリケーションのスレッドモデルをお読みください。リンクは、WPFスレッドモデルを説明するページに移動します。ただし、Windowsフォームは同じ考え方を利用しています。

UIスレッド

  • System.Windows.Forms.Controlとそのサブクラスのメンバーへのアクセスが許可されているスレッドは1つだけ(UIスレッド)です。
  • UIスレッド以外のスレッドからSystem.Windows.Forms.Controlのメンバーにアクセスしようとすると、スレッド間例外が発生します。
  • スレッドは1つしかないため、すべてのUI操作はそのスレッドの作業項目としてキューに入れられます。

ここに画像の説明を入力してください

ここに画像の説明を入力してください

BeginInvokeおよびInvokeメソッド

  • UIスレッドがそこで使用されるため、呼び出されるメソッドの計算オーバーヘッドはイベントハンドラーメソッドの計算オーバーヘッドと同様に小さくなければなりません。これがSystem.Windows.Forms.Control.Invokeであるか、System.Windows.Forms.Control.BeginInvokeであるかに関係なく。
  • 負荷の高い演算を実行するには、常に別のスレッドを使用します。.NET 2.0以降、BackgroundWorkerはWindowsフォームでの計算コストの高い操作の実行に専念しています。ただし、新しいソリューションでは、ここで説明するようにasync-awaitパターンを使用する必要があります
  • System.Windows.Forms.Control.InvokeまたはSystem.Windows.Forms.Control.BeginInvokeメソッドは、ユーザーインターフェイスを更新する場合にのみ使用してください。これらを重い計算に使用すると、アプリケーションがブロックします。

ここに画像の説明を入力してください

呼び出す

ここに画像の説明を入力してください

BeginInvoke

ここに画像の説明を入力してください

コードソリューション

C#の別のスレッドからGUIを更新する方法についての質問の回答を読んでください。C#5.0および.NET 4.5の場合、推奨されるソリューションはこちらです。



72

あなただけ使用するInvokeか、BeginInvoke仕事の最低限の部分のためのUIを変更するために必要。「重い」メソッドは別のスレッドで(たとえばを介してBackgroundWorker)実行する必要がありますが、Control.Invoke/ を使用しControl.BeginInvokeてUIを更新するだけです。これにより、UIスレッドはUIイベントなどを自由に処理できるようになります。

WinFormsの例については、私のスレッディング記事を参照してください。この記事は、現場に到着する前に書かれたものであり、その点では更新していないと思います。コールバックを少し単純化するだけです。BackgroundWorkerBackgroundWorker


この私の状態でここに。私もUIを変更しません。子スレッドから現在の値にアクセスするだけです。実装する提案
ハードウェア

1
プロパティにアクセスするだけでも、UIスレッドにマーシャリングする必要があります。値にアクセスするまでメソッドを続行できない場合は、値を返すデリゲートを使用できます。しかし、はい、UIスレッドを経由します。
ジョンスキート

こんにちはジョン、私はあなたが正しい方向に向かっていると信じています。はい、それがなければ値を必要とするので、先に進むことができません。「値を返すデリゲートを使用する」ことについて詳しく説明してください。ありがとう
Prera​​k K

1
Func <string>などのデリゲートを使用します。string text = textbox1.Invoke((Func <string>)()=> textbox1.Text); (これは、C#3.0を使用していることを前提としています。それ以外の場合は、匿名メソッドを使用できます。)
Jon Skeet

45

今はもう遅すぎます。しかし、今日でも、クロススレッドコントロールへのアクセスに問題がある場合はどうでしょうか。これは日付までの最短の回答です:P

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

これは、スレッドからフォームコントロールにアクセスする方法です。


1
これは私に与えますInvoke or BeginInvoke cannot be called on a control until the window handle has been created。私はここで
rupweb

42

私はこの問題を抱えていFileSystemWatcherて、次のコードが問題を解決したことがわかりました:

fsw.SynchronizingObject = this

次に、コントロールは現在のフォームオブジェクトを使用してイベントを処理するため、同じスレッドに配置されます。


2
これは私のベーコンを救った。VB.NETで私が使用した.SynchronizingObject = Me
codecodingcoding

20

フォームに関連するすべてのメソッド内に散らばる必要のあるチェックアンドインボークコードは、冗長すぎて不必要であることがわかりました。以下は、完全に削除できる単純な拡張メソッドです。

public static class Extensions
{
    public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del) 
        where TControlType : Control
        {
            if (control.InvokeRequired)
                control.Invoke(new Action(() => del(control)));
            else
                del(control);
    }
}

そして、あなたは単にこれを行うことができます:

textbox1.Invoke(t => t.Text = "A");

もういじりません-シンプル。


ここにないもの
Rawat

tこの場合の@Rawat はtextbox1-引数として渡されます
Rob

17

.NETのコントロールは一般にスレッドセーフではありません。つまり、コントロールが存在するスレッド以外のスレッドからコントロールにアクセスしないでください。これを回避するには、以下を呼び出す必要があります。するには、2番目のサンプルが試行しているコントロールます。

ただし、あなたの場合は、実行時間の長いメソッドをメインスレッドに戻すだけです。もちろん、それはあなたが本当にやりたいことではありません。これを少し考え直して、メインスレッドで行うすべてのことをあちこちで簡単にプロパティを設定する必要があります。



10

Async / Awaitとコールバックを使用した新しい外観。プロジェクトで拡張メソッドを保持している場合、必要なコードは1行だけです。

/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
    /// <summary>
    /// No more delegates, background workers etc. just one line of code as shown below
    /// Note it is dependent on the XTask class shown next.
    /// </summary>
    public async void ExampleMethod()
    {
        //Still on GUI/Original Thread here
        //Do your updates before the next line of code
        await XTask.RunAsync(() =>
        {
            //Running an asynchronous task here
            //Cannot update GUI Thread here, but can do lots of work
        });
        //Can update GUI/Original thread on this line
    }
}

/// <summary>
/// A class containing extension methods for the Task class 
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
    /// <summary>
    /// RunAsync is an extension method that encapsulates the Task.Run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}

Try / Catchステートメントでラップするなど、Extensionメソッドに他のものを追加して、呼び出し元が完了後に返すタイプ、呼び出し元への例外コールバックを通知できるようにすることができます。

キャッチトライ、自動例外ロギング、コールバックの追加

    /// <summary>
    /// Run Async
    /// </summary>
    /// <typeparam name="T">The type to return</typeparam>
    /// <param name="Code">The callback to the code</param>
    /// <param name="Error">The handled and logged exception if one occurs</param>
    /// <returns>The type expected as a competed task</returns>

    public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
    {
       var done =  await Task<T>.Run(() =>
        {
            T result = default(T);
            try
            {
               result = Code("Code Here");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unhandled Exception: " + ex.Message);
                Console.WriteLine(ex.StackTrace);
                Error(ex);
            }
            return result;

        });
        return done;
    }
    public async void HowToUse()
    {
       //We now inject the type we want the async routine to return!
       var result =  await RunAsync<bool>((code) => {
           //write code here, all exceptions are logged via the wrapped try catch.
           //return what is needed
           return someBoolValue;
       }, 
       error => {

          //exceptions are already handled but are sent back here for further processing
       });
        if (result)
        {
            //we can now process the result because the code above awaited for the completion before
            //moving to this statement
        }
    }

10

これは、このエラーを解決するための推奨される方法ではありませんが、すばやく抑制できます。私はプロトタイプやデモにこれを好みます。追加

CheckForIllegalCrossThreadCalls = false

Form1()コンストラクタ。


9

別のスレッドからオブジェクトを変更する最も簡単な(私の意見では)方法に従います。

using System.Threading.Tasks;
using System.Threading;

namespace TESTE
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Action<string> DelegateTeste_ModifyText = THREAD_MOD;
            Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
        }

        private void THREAD_MOD(string teste)
        {
            textBox1.Text = teste;
        }
    }
}

シンプル!感謝します。
Ali Esmaeili


7

xamarin stuidio外のビジュアルスタジオwinformsプロトタイププロジェクトでiOS-Phoneモノタッチアプリコントローラーをプログラミングしているときに、この必要性を見つけました。xamarin studioよりもVSでのプログラミングをできるだけ優先して、コントローラーを電話のフレームワークから完全に切り離したいと考えました。これにより、AndroidやWindows Phoneなどの他のフレームワークにこれを実装すると、将来の使用がはるかに容易になります。

すべてのボタンクリックの背後にあるクロススレッドスイッチングコードを処理する負担なしに、GUIがイベントに応答できるソリューションが必要でした。基本的には、クラスコントローラーに処理を任せて、クライアントコードをシンプルに保ちます。GUIには多くのイベントがあり、クラス内の1か所でイベントを処理するほうがクリーンな場合があります。私はマルチシアターのエキスパートではありません。これに欠陥がある場合はお知らせください。

public partial class Form1 : Form
{
    private ExampleController.MyController controller;

    public Form1()
    {          
        InitializeComponent();
        controller = new ExampleController.MyController((ISynchronizeInvoke) this);
        controller.Finished += controller_Finished;
    }

    void controller_Finished(string returnValue)
    {
        label1.Text = returnValue; 
    }

    private void button1_Click(object sender, EventArgs e)
    {
        controller.SubmitTask("Do It");
    }
}

GUIフォームは、コントローラーが非同期タスクを実行していることを認識していません。

public delegate void FinishedTasksHandler(string returnValue);

public class MyController
{
    private ISynchronizeInvoke _syn; 
    public MyController(ISynchronizeInvoke syn) {  _syn = syn; } 
    public event FinishedTasksHandler Finished; 

    public void SubmitTask(string someValue)
    {
        System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
    }

    private void submitTask(string someValue)
    {
        someValue = someValue + " " + DateTime.Now.ToString();
        System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.

        if (Finished != null)
        {
            if (_syn.InvokeRequired)
            {
                _syn.Invoke(Finished, new object[] { someValue });
            }
            else
            {
                Finished(someValue);
            }
        }
    }
}

6

作業しているオブジェクトにない場合の代替方法は次のとおりです

(InvokeRequired)

これは、メインフォームにあるオブジェクトを持つメインフォーム以外のクラスでメインフォームを操作しているが、InvokeRequiredがない場合に便利です。

delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);

private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
    MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}

public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
    objectWithoutInvoke.Text = text;
}

上記と同じように機能しますが、invokerequiredを持つオブジェクトがなくてもMainFormにアクセスできる場合は、別のアプローチになります。


5

前の回答と同じ行に沿っていますが、クロススレッドの呼び出し例外なしですべての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;
}


5

たとえば、UIスレッドのコントロールからテキストを取得するには:

Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String

Private Function GetControlText(ByVal ctl As Control) As String
    Dim text As String

    If ctl.InvokeRequired Then
        text = CStr(ctl.Invoke(
            New GetControlTextInvoker(AddressOf GetControlText), ctl))
    Else
        text = ctl.Text
    End If

    Return text
End Function

3

同じ質問:how-to-update-the-gui-from-another-thread-in-c

二通り:

  1. e.resultの戻り値を使用して、backgroundWorker_RunWorkerCompletedイベントのテキストボックス値を設定します。

  2. これらの種類の値を別のクラス(データホルダーとして機能する)に保持する変数を宣言します。このクラスの静的インスタンスを作成し、任意のスレッドでアクセスできるようにします。

例:

public  class data_holder_for_controls
{
    //it will hold value for your label
    public  string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();
    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread 
    }

    public static void perform_logic()
    {
        //put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            //statements here
        }
        //set result in status variable
        d1.status = "Task done";
    }
}



0

この問題を回避するためのシンプルで再利用可能な方法。

延長方法

public static class FormExts
{
    public static void LoadOnUI(this Form frm, Action action)
    {
        if (frm.InvokeRequired) frm.Invoke(action);
        else action.Invoke();
    }
}

使用例

private void OnAnyEvent(object sender, EventArgs args)
{
    this.LoadOnUI(() =>
    {
        label1.Text = "";
        button1.Text = "";
    });
}

-3

スレッド間操作には2つのオプションがあります。

Control.InvokeRequired Property 

そして2番目は使用することです

SynchronizationContext Post Method

Control.InvokeRequiredは、SynchronizationContextをどこでも使用できる一方で、Controlクラスから継承された作業用コントロールの場合にのみ役立ちます。いくつかの有用な情報は次のリンクとしてあります

スレッド間更新UI | 。ネット

SynchronizationContextを使用したスレッド間更新UI | 。ネット

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.