639

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

  1. ユーザー コントロールをホストするメイン フォームがあります。
  2. ユーザーコントロールは、メソッドを直接呼び出すと、UserControl_Loadロードメソッドの実行中にUIが応答しなくなるなど、重いデータ操作を行います。
  3. これを克服するために、別のスレッドにデータをロードします(既存のコードをできるだけ変更しようとしています)
  4. データをロードするバックグラウンド ワーカー スレッドを使用しました。完了すると、作業が完了したことをアプリケーションに通知します。
  5. 今、本当の問題が来ました。すべての UI (メイン フォームとその子ユーザー コントロール) は、プライマリ メイン スレッドで作成されました。ユーザーコントロールの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.
    }
}

それが与えた例外は

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

これについてもっと知るために、グーグルで調べたところ、次のコードを使用するような提案が出てきました

コード 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... 振り出しに戻ったようです。アプリケーションが再び応答しなくなります。1行目のif条件の実行によるものと思われます。ロード タスクは、私が生成した 3 番目のスレッドではなく、親スレッドによって再度実行されます。

この認識が正しかったのか間違っていたのか、私にはわかりません。私はスレッド化が初めてです。

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

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

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

4

23 に答える 23

468

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;
        }
    }
}
于 2008-09-26T21:22:30.303 に答える
204

UI のスレッド モデル

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

UI スレッド

  • System.Windows.Forms.Controlとそのサブクラス メンバーにアクセスできるスレッド (UI スレッド) は 1 つだけです。
  • 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

ここに画像の説明を入力

コード ソリューション

How to update the GUI from another thread in C#?という質問の回答を読んでください。. C# 5.0 および .NET 4.5 の場合、推奨されるソリューションはこちらです。

于 2013-09-26T08:34:20.010 に答える
75

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

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

于 2008-09-26T21:21:09.683 に答える
44

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

fsw.SynchronizingObject = this

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

于 2009-01-13T02:13:35.057 に答える
21

フォームに関連するすべてのメソッド内に散らばる必要があるチェックと呼び出しのコードは、あまりにも冗長で不要であることがわかりました。これを完全になくすことができる簡単な拡張メソッドを次に示します。

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

もういじる必要はありません - シンプルです。

于 2016-05-02T13:42:54.123 に答える
18

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

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

于 2008-09-26T21:19:43.897 に答える
15

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

于 2009-04-27T05:18:06.263 に答える
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 メソッドに他のものを追加できます。これにより、呼び出し元が完了後に返す型を指定できるようになり、呼び出し元への例外コールバックが可能になります。

Try Catch、Auto Exception Logging、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
        }
    }
于 2016-05-16T01:55:29.513 に答える
8

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

于 2008-09-26T21:41:02.053 に答える
7

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

ボタンをクリックするたびにコードを切り替えるクロス スレッドを処理するという負担を負うことなく、GUI がイベントに応答できるソリューションが必要でした。基本的に、クライアント コードをシンプルに保つために、クラス コントローラーに処理させます。クラス内の 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);
            }
        }
    }
}
于 2013-09-11T18:29:57.813 に答える
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 にアクセスできる場合は別のアプローチになります。

于 2012-05-24T22:49:15.387 に答える
5

以前の回答と同じ行に沿っていますが、クロススレッド呼び出し例外なしですべてのコントロールプロパティを使用できるようにする非常に短い追加です。

ヘルパー メソッド

/// <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;
}
于 2014-10-22T11:29:44.887 に答える
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
于 2014-02-05T12:06:47.483 に答える
5

この問題を回避するための簡単で再利用可能な方法。

延長方法

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 = "";
    });
}
于 2020-05-08T18:44:09.587 に答える