web-dev-qa-db-ja.com

別のスレッドからTextBoxに書き込みますか?

C#Windows Formアプリケーションがスレッドからテキストボックスに書き込む方法を理解できません。たとえば、Program.csには、フォームを描画する標準のmain()があります。

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new Form1());
}

次に、Form1.csにあります。

public Form1()
{
    InitializeComponent();

    new Thread(SampleFunction).Start();
}

public static void SampleFunction()
{
    while(true)
        WindowsFormsApplication1.Form1.ActiveForm.Text += "hi. ";
}

私はこれについて完全に間違っていますか?

[〜#〜] update [〜#〜]

これは、bendeweyから提供された実際のコードサンプルです。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        new Thread(SampleFunction).Start();
    }

    public void AppendTextBox(string value)
    {
        if (InvokeRequired)
        {
            this.Invoke(new Action<string>(AppendTextBox), new object[] {value});
            return;
        }
        textBox1.Text += value;
    }

    void SampleFunction()
    {
        // Gets executed on a seperate thread and 
        // doesn't block the UI while sleeping
        for(int i = 0; i<5; i++)
        {
            AppendTextBox("hi.  ");
            Thread.Sleep(1000);
        }
    }
}
60
David

MainFormで、InvokeRequiredをチェックするテキストボックスを設定する関数を作成します。

public void AppendTextBox(string value)
{
    if (InvokeRequired)
    {
        this.Invoke(new Action<string>(AppendTextBox), new object[] {value});
        return;
    }
    ActiveForm.Text += value;
}

ただし、静的メソッドでは、単に呼び出すことはできません。

WindowsFormsApplication1.Form1.AppendTextBox("hi. ");

あなたはどこかにForm1への静的な参照を持っている必要がありますが、これは本当にお勧めでも必要でもありません。

AppendTextBox("hi. ");

異なるスレッドに追加し、必要に応じてInvoke呼び出しを使用してUIにマーシャリングされます。

Full Sample

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        new Thread(SampleFunction).Start();
    }

    public void AppendTextBox(string value)
    {
        if (InvokeRequired)
        {
            this.Invoke(new Action<string>(AppendTextBox), new object[] {value});
            return;
        }
        textBox1.Text += value;
    }

    void SampleFunction()
    {
        // Gets executed on a seperate thread and 
        // doesn't block the UI while sleeping
        for(int i = 0; i<5; i++)
        {
            AppendTextBox("hi.  ");
            Thread.Sleep(1000);
        }
    }
}
63
bendewey

またはあなたは好きなことができます

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        new Thread( SampleFunction ).Start();
    }

    void SampleFunction()
    {
        // Gets executed on a seperate thread and 
        // doesn't block the UI while sleeping
        for ( int i = 0; i < 5; i++ )
        {
            this.Invoke( ( MethodInvoker )delegate()
            {
                textBox1.Text += "hi";
            } );
            Thread.Sleep( 1000 );
        }
    }
}
14
Chris

コントロールが更新されるまで本当に待つ必要がない限り、BeginInvokeの代わりにInvokeをできるだけ頻繁に使用します(この例ではそうではありません)。 BeginInvokeは、WinFormsメッセージキューにデリゲートを投稿し、呼び出し元のコードをすぐに続行させます(この場合、SampleFunctionのforループ)。 Invokeはデリゲートをポストするだけでなく、デリゲートが完了するまで待機します。

したがって、例のメソッドAppendTextBoxでは、InvokeBeginInvokeに置き換えます。

public void AppendTextBox(string value)
{
    if (InvokeRequired)
    {
        this.BeginInvoke(new Action<string>(AppendTextBox), new object[] {value});
        return;
    }
    textBox1.Text += value;
}

さて、さらにもっと凝りたい場合は、SynchronizationContextクラスもあります。これは、基本的にControl.Invoke/Control.BeginInvokeと同じことを可能にしますが、WinFormsコントロール参照を必要としないという利点があります。知られています。 ここSynchronizationContextに関する小さなチュートリアルです。

13
Mike

コントロールを所有するスレッドからアクションを実行する必要があります。

それは私がコードノイズを追加しすぎることなくそれをやっている方法です:

control.Invoke(() => textBox1.Text += "hi");

Invoke overloadは Lokad Shared Libraries からの単純な拡張です。

/// <summary>
/// Invokes the specified <paramref name="action"/> on the thread that owns     
/// the <paramref name="control"/>.</summary>
/// <typeparam name="TControl">type of the control to work with</typeparam>
/// <param name="control">The control to execute action against.</param>
/// <param name="action">The action to on the thread of the control.</param>
public static void Invoke<TControl>(this TControl control, Action action) 
  where TControl : Control
{
  if (!control.InvokeRequired)
  {
    action();
  }
  else
  {
    control.Invoke(action);
  }
}
5
Rinat Abdullin

さらに簡単なのは、BackgroundWorkerコントロールを使用することです...

3
Chris KL

最も単純な、デリゲートを気にしない

if(textBox1.InvokeRequired == true)
    textBox1.Invoke((MethodInvoker)delegate { textBox1.Text = "Invoke was needed";});

else
    textBox1.Text = "Invoke was NOT needed"; 
3
marsh-wiggle

CrossThreadExceptionと別のスレッドからテキストボックスへの書き込みを避けるために私がしたことは次のとおりです。

ここに私の_Button.Click_関数があります-ランダムな数のスレッドを生成し、そのワーカースレッドにいる間にgetID()メソッドとTextBox値を呼び出してIDsを取得します。

_private void btnAppend_Click(object sender, EventArgs e) 
{
    Random n = new Random();

    for (int i = 0; i < n.Next(1,5); i++)
    {
        label2.Text = "UI Id" + ((Thread.CurrentThread.ManagedThreadId).ToString());
        Thread t = new Thread(getId);
        t.Start();
    }
}
_

getId(workerThread)コードは次のとおりです。

_public void getId()
{
    int id = Thread.CurrentThread.ManagedThreadId;
    //Note that, I have collected threadId just before calling this.Invoke
    //method else it would be same as of UI thread inside the below code block 
    this.Invoke((MethodInvoker)delegate ()
    {
        inpTxt.Text += "My id is" +"--"+id+Environment.NewLine; 
    });
}
_
2
Iqra.

Control.BeginInvoke メソッドをご覧ください。ポイントは、別のスレッドからUIコントロールを更新しないことです。 BeginInvokeは、コントロールのUIスレッド(この場合はフォーム)に呼び出しをディスパッチします。

フォームを取得するには、サンプル関数から静的修飾子を削除し、MSDNの例に示すようにthis.BeginInvoke()を使用します。

1
Dan C.