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


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

<オール>
  • いくつかのユーザー コントロールをホストするメイン フォームがあります。
  • ユーザー コントロールは大量のデータ操作を実行するため、UserControl_Load メソッドを直接呼び出すと、load メソッドの実行中に UI が応答しなくなります。
  • これを克服するために、別のスレッドにデータをロードします (既存のコードをできるだけ変更しないようにしています)
  • tr スレッドを使用しましたバックグラウンドでダウンしてデータをロードし、完了するとアプリケーションにジョブの完了を通知します。
  • ここで、本当の問題が発生しました。ユーザー インターフェイス全体 (メイン フォームとその子ユーザー コントロール) は、メイン メイン スレッドで作成されました。ユーザー コントロールの LOAD メソッドでは、ユーザー コントロール内の一部のコントロール (テキスト ボックスなど) の値に基づいてデータを取得します。
  • 擬似コードは次のようになります:

    <強い>コード 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.
        }
    }
    

    それが与えた例外は

    <ブロック引用>

    クロススレッド操作が無効です: 作成されたスレッド以外のスレッドからアクセスされたscamtrail.

    これについてもっと知るために、私はグーグルで検索し、次のコードの使用方法を提案しました

    コード 2

    UserContrl1_LoadDataMethod()
    {
        if (InvokeRequired) // Line #1
        {
            this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
            return;
        }
    
        if (textbox1.text == "MyName") // Now it won't give an exception
        {
        //Load data correspondin to "MyName"
            //Populate a globale variable List<string> which will be binded to grid at some later stage
        }
    }
    

    しかし、まだ振り出しに戻ったような気がします。再びアプリ 応答を停止します。と思われる行 n の実行まで。 #1 状態の場合。ロード タスクは、3 番目に生成したスレッドではなく、メイン スレッドによって再度実行されます。

    これを良く理解したのか、悪く理解したのかわかりません.

    これを解決するにはどうすればよいですか?また、クラッシュした場合に行 #1 を実行するとどのような影響がありますか?

    状況: コントロールの値に基づいてグローバル変数にデータをロードしたいと考えています。子スレッドのコントロールの値を変更したくありません。からは絶対にやらない二次スレッド。

    次に、対応するデータをデータベースから取得できるように、値にのみアクセスします。



    ------------答え-----------

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


    ------------に答える------------

    ユーザー インターフェイス スレッド モデルイオ

    アプリケーション UI の Threading Model を読んで (古い VB リンクはこちら)、基本を理解してください。このリンクは、WPF スレッド モデルを説明するページに移動します。ただし、Windows フォームは同じ考え方を使用します。

    UI スレッド

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

    • UI スレッドの作業がない場合、関連のないコンピューティングで使用できるアイドル g スレッドが存在します。ユーザー インターフェースを使用します。
    • 前述の抜け穴を使用するには、System.Windows.Forms.Control.Invoke または System.Windows.Forms.Control.BeginInvoke: メソッドを使用してください

    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.Begi メソッドを使用するユーザー インターフェイスを更新するためだけに nInvoke を実行します。それらを共同重み付けに使用すると、アプリは以下をブロックします:

    召喚

    • System.Windows.Forms.Control.Invoke により、別のスレッドが呼び出されたメソッドの完了を待機します:

    召喚を開始

    • System.Windows.Forms.Control.BeginInvoke は、呼び出されたメソッドが完了するまで切り離されたスレッドを待機させません:

    太陽コード表記

    C# で別のスレッドから GUI を更新する方法は? という質問への回答をお読みください。 C# 5.0 および .NET4.5 の推奨ソリューションはこちらです。



    ------------に答える------------

    UI の変更に必要な最小限の作業にのみ、Invoke または BeginInvoke を使用する必要があります。 「重い」メソッドは別のスレッドで (たとえば、BackgroundWorker を介して) 実行する必要がありますが、Control.Invoke/Control.BeginInvoke を使用して実行するだけです。ユーザー インターフェイスを滑らかにします。そうすれば、UI スレッドは UI イベントなどを自由に処理できるようになります。

    この記事は BackgroundWorker が登場する前に書かれたものですが、WinForms の例については私のスレッド作成記事を参照してください。 BackgroundWorker はコールバックを少し単純化します。



    ------------に答える------------

    もう手遅れであることはわかっています。ただし、今日でも、クロススレッド コントロールへのアクセスに問題がある場合は.これは現在までの最短の回答です:P

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

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



    ------------に答える------------

    FileSystemWatcher でこの問題が発生しましたが、次のコードで問題が解決したことがわかりました:

    fsw.SynchronizingObject = this

    次に、コントロールはイベントを処理する現在のフォーム オブジェクトであるため、同じスレッドになります。



    ------------に答える------------

    フォーム関連のすべてのメソッドに含める必要があるチェックと呼び出しのコードは、冗長すぎて不必要だと思います。これを完全に削除できる簡単な拡張メソッドを次に示します。

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

    時間を無駄にすることはもうありません - シンプルです。



    ------------に答える------------

    .NET のコントロールは通常、スレッドセーフではありません。つまり、コントロールが存在するスレッド以外のスレッドからコントロールにアクセスしてはいけません。これを回避するには、コントロールを 呼び出す 必要があります。これは、2 番目のサンプルが実行しようとしていることです。

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



    ------------に答える------------

    UI クロススレッドの問題に対する最もクリーンな (そして適切な) 解決策は、SynchronizationContext を使用することです。マルチスレッド アプリケーションでの UI 呼び出しの同期に関する記事を参照してください。非常によく説明されています。



    ------------に答える------------

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

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


    ------------に答える------------

    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 メソッドに他のものを追加できます。これにより、呼び出し元が返す型を通知できます。完了後に確認し、呼び出し元に例外をスローします:

    Try Catch、自動例外ログ、コールバックを追加

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


    ------------に答える------------

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

    CheckForIllegalCrossThreadCalls = false
    

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



    ------------に答える------------

    Backgroundworker の例を見てください:
    http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx 特に、UI レイヤーとどのように相互作用するか。あなたの投稿に基づいて、これはあなたの問題に答えているようです.



    ------------に答える------------

    作業中のオブジェクトに

    がない場合の代替方法は次のとおりです。
    (InvokeRequired)
    

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

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

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



    ------------に答える------------

    Vis Winforms プロトタイプ プロジェクトでシングル タッチ iOS-Phone アプリ コントローラーをプログラミングしているときに、これが必要であることがわかりました。Xamarin Studio の外部にある ual Studio。可能な限り Xamarin Studio よりも VS でプログラミングすることを好み、コントローラーを電話フレームワークから完全に切り離したいと考えました。このようにして、Android や Windows Phone などの他のフレームワークにこれを実装すると、将来の使用がはるかに簡単になります。

    私は、GUI がボタン クリックの背後にあるクロススレッド スイッチング コードを処理する負担なしに、イベントに応答できるソリューションを求めていました。基本的に、dクライアント コードをシンプルに保つために、クラス コントローラーにそれを処理させます。クラス内の 1 か所でそれらを処理できれば、GUI で多くのイベントを処理できるようになる可能性があります。私はマルチスレッドの専門家ではありません。これに問題がある場合はお知らせください。

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


    ------------に答える------------

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

    拡張方法

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


    ------------に答える------------

    前の回答と同じように、 ただし、クロススレッド呼び出し例外なしですべての CoControl プロパティを使用できるようにする非常に短い追加です。

    ヘルパー メソッド

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


    ------------に答える------------

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


    ------------に答える------------

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


    ------------に答える------------

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

    2 つの方法:

    1. e.result で値を返し、それを使用して backgroundWorker_RunWorkerCompleted イベントでテキスト ボックスの値を設定します

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

    例:

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


    ------------に答える------------

    これを使用するだけです:

    this.Invoke((MethodInvoker)delegate
                {
                    YourControl.Property= value; // runs thread safe
                });
    


    ------------に答える------------

    行動的; //クラス内で宣言

    label1.Invoke(y=()=>label1.Text="text");



    ------------に答える------------

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

    Control.InvokeRequired Property 
    

    2 つ目は

    を使用することです
    SynchronizationContext Post Method
    

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

    クロススレッド更新ユーザー インターフェース | .ネット

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

    タグ:

    関連記事:

    c++ - call(Double X) を func2(double X) に #define する方法

    python - Djangoのpre_save信号:挿入か更新かを知るにはどうすればよいですか?