c# - 表示 - vb.net 有効ではないスレッド間の操作 コントロールが作成されたスレッド以外のスレッドからコントロール がアクセスされました。




クロススレッド操作が有効でない:作成されたスレッド以外のスレッドからアクセスされたコントロール (14)

UIのスレッドモデル

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

UIスレッド

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

BeginInvokeメソッドとInvokeメソッド

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

呼び出す

BeginInvoke

コードソリューション

質問の答えを読むC#の別のスレッドからGUIを更新するには? 。 C#5.0と.NET 4.5については、推奨されるソリューションがhereありhere 。

私はシナリオを持っています。 (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
    }
}

しかし、しかし...私は正方形に戻っているようです。 アプリケーションは再び応答しなくなります。 条件1の行が実行されているためと思われます。 読み込みタスクは、親スレッドによって行われ、私が生成した3番目のスレッドではありません。

私はこれが正しいか間違っているかどうかわかりません。 私はスレッドを初めて使う人です。

これを解決するにはどうすればいいのでしょうか、ブロックすればライン#1の実行の効果は何ですか?

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

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


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

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


Backgroundworkerの例を見る必要があります:
BackgroundWorker特に、UIレイヤーとのやりとり方法。 あなたの投稿に基づいて、これはあなたの問題に答えるようです。


UIを変更するために必要な作業の最小限の作業に対してのみ、InvokeまたはBeginInvokeを使用します。 あなたの "重い"メソッドは別のスレッド(例えばBackgroundWorker経由)で実行する必要がありますが、Control.Invoke / Control.BeginInvokeを使用してUIを更新するだけです。 そうすれば、UIスレッドはUIイベントなどを自由に処理できます。

BackgroundFormerがシーンに到着する前に記事が書かれていたにもかかわらず、 WinFormsの例については私のスレッディング記事を見てください。私はその点でそれを更新していないのではないかと思います。 BackgroundWorkerは単にコールバックを単純化するだけです。


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;
        }
    }
}

同じ質問:どのように更新する - gui-from-another-thread-in-c

二通り:

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

  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";
    }
}

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

CheckForIllegalCrossThreadCalls = false

Form1()コンストラクターで。


たとえば、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

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

Control.InvokeRequired Property 

もう1つは使用することです

SynchronizationContext Post Method

Control.InvokeRequiredは、Controlクラスから継承した作業コントロールがSynchronizationContextをどこでも使用できる場合にのみ便利です。 いくつかの有益な情報は以下のリンクです

クロススレッドの更新UI | 。ネット

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


フォームに関連するすべてのメソッドの中で散らばっている必要のあるチェック・アンド・インヴェーク・コードは、あまりにも冗長で不要になることがわかります。 ここでは単純な拡張メソッドを使用して、完全に取り除くことができます。

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");

これ以上のことはいらない - シンプル。


以前の回答と同じ行にありますが、非常に短い追加で、クロススレッド呼び出しの例外を除いてすべての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;
}

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

fsw.SynchronizingObject = this

コントロールは、現在のフォームオブジェクトを使用してイベントを処理します。したがって、同じスレッドになります。


私は今は遅すぎることを知っている。 しかし、今日でさえ、あなたがクロススレッドコントロールにアクセスする際に問題がある場合は? これは、日付までの最短の答えです:P

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

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


this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));






invoke