504

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

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

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

コード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条件のif#1行目の実行によるものと思われます。ローディングタスクは、親スレッドによって行われ、私が生成した3番目のタスクではありません。

私はこれが正しいのか間違っているのかを知りません。私はスレッドに慣れていません。

どうすればこれを解決できますか?また、1行目のifブロックの実行による影響は何ですか?

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

そのため、対応するデータをデータベースから取得できるように値にアクセスするだけです。

20 답변


383

に従ってPrerak 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;
        }
    }
}


  • 意志control.BeginInvoke仕事も? - newbieguy
  • C#プログラミングを行って以来、しばらく時間が経っていますが、MSDNの記事と私の愚かな知識に基づいているように見えます。 - Jeff Hubbard
  • 違いは、BeginInvoke()は非同期で、Invoke()は同期的に実行されることです。stackoverflow.com/questions/229554/… - frzsombor

148

UIのスレッドモデル

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

UIスレッド

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

enter image description here

enter image description here

BeginInvokeメソッドとInvokeメソッド

  • UIスレッドがそこで使用されるので、呼び出されるメソッドの計算オーバーヘッドは、イベントハンドラメソッドの計算オーバーヘッドと同じくらい小さくする必要があります。これは、ユーザー入力を処理する責任があります。これに関係なくSystem.Windows.Forms.Control.InvokeまたはSystem.Windows.Forms.Control.BeginInvoke
  • 計算コストの高い操作を実行するには、常に別のスレッドを使用します。 .NET 2.0以降BackgroundWorkerWindowsフォームでは、高価な演算処理を実行するためのものです。ただし、新しいソリューションでは、説明したように非同期待機パターンを使用する必要があります。ここに
  • つかいますSystem.Windows.Forms.Control.InvokeまたはSystem.Windows.Forms.Control.BeginInvokeユーザーインターフェイスを更新するためのメソッドのみです。あなたが重い計算のためにそれらを使うならば、あなたのアプリケーションはブロックするでしょう:

enter image description here

呼び出す

enter image description here

BeginInvoke

enter image description here

コードソリューション

質問に対する回答を読むC#で別のスレッドからGUIを更新する方法?。 C#5.0および.NET 4.5の場合、推奨される解決策はここに


69

UIの変更に必要な最低限の作業には、InvokeまたはBeginInvokeを使用するだけです。あなたの "重い"メソッドは別のスレッドで(例えばBackgroundWorkerを介して)実行されるべきですが、それからちょうどUIを更新するためにControl.Invoke / Control.BeginInvokeを使用するべきです。そのようにあなたのUIスレッドはUIイベントなどを自由に扱うことができます。

私を見てスレッド記事のためにWinFormsの例 - 記事はBackgroundWorkerが登場する前に書かれたものですが、その点については更新していません。 BackgroundWorkerはコールバックを少し単純化するだけです。


  • ここに私のこの状態で。私もUIを変更していません。子スレッドから現在の値にアクセスするだけです。実装する提案hw - Prerak K
  • プロパティにアクセスするだけでも、依然としてUIスレッドにマーシャリングする必要があります。値にアクセスするまでメソッドが続行できない場合は、値を返すデリゲートを使用できます。しかし、はい、UIスレッドを経由してください。 - Jon Skeet
  • こんにちはジョン、私はあなたが正しい方向に向かっていると信じています。はい私はそれ以上の価値を必要とします。そのことを詳しく説明してください。値を返すデリゲートを使用しています。ありがとう - Prerak K
  • Func< string>:string text = textbox1.Invoke((Func< string>)()=> textbox1.Text);のようなデリゲートを使用してください。 (C#3.0を使用していることを前提としています。それ以外の場合は匿名の方法を使用できます。) - Jon Skeet

38

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

fsw.SynchronizingObject = this

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


  • これは私のベーコンを救った。 VB.NETでは私が使った.SynchronizingObject = Me - codingcoding

14

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

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


14

私は、フォームに関連するすべてのメソッド内でチェックアンドインボークコードを散らす必要があり、あまりにも冗長で不要なものにしています。これはあなたがそれを完全に廃止することを可能にする簡単な拡張方法です:

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

これ以上面倒なことはありません - 簡単


  • ' t'とは何ですかここに - Rawat
  • @ケアtこの場合はtextbox1 - 引数として渡されています - Rob

14

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

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

これはどのように私がスレッドから任意のフォームコントロールにアクセスするかです。


  • これは私にくれますInvoke or BeginInvoke cannot be called on a control until the window handle has been created。解決しましたここに - rupweb

11

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


7

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

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


7

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

ExtensionメソッドにTry / Catchステートメントでラップするなど、他のものを追加することもできます。これにより、呼び出し側は、完了後に返される型、呼び出し側への例外コールバックを伝えることができます。

試行キャッチ、自動例外ロギング、およびCallBackの追加

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


6

Backgroundworkerの例を見てください。

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


6

私はxamarin stuidioの外のビジュアルスタジオのwinformsプロトタイププロジェクトでiOS-Phoneモノタッチアプリケーションコントローラをプログラムしている間、これの必要性を見つけました。可能な限りxamarinスタジオよりもVSでプログラムするのを好むので、私はコントローラーを電話のフレームワークから完全に切り離したいと思いました。この方法で、AndroidやWindows Phoneなどの他のフレームワークにこれを実装すると、将来の使用ではるかに簡単になります。

ボタンをクリックするたびにクロススレッドの切り替えコードを処理しなくても、GUIがイベントに応答できるソリューションが必要でした。基本的にクラスコントローラにそれを処理させてクライアントコードを単純にします。あなたはおそらくあなたがクラスの一箇所でそれを扱うことができるかのように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);
            }
        }
    }
}


6

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

CheckForIllegalCrossThreadCalls = false

Form1()コンストラクタ。


4

これはあなたが作業しているオブジェクトが持っていない場合の代替方法です。

(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にアクセスできる場合は、別の方法になります。


3

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


3

前の答えと同じように、 しかし、スレッド間の呼び出し例外を発生させることなく、すべての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;
}


3

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


1

同じ質問:他のスレッドからのguiの更新方法

二通り:

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


1

アクションy //クラス内で宣言

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


-2

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

Control.InvokeRequired Property 

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

SynchronizationContext Post Method

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

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

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

リンクされた質問


関連する質問

最近の質問