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);
}
}
}
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);
}
}
}
またはあなたは好きなことができます
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 );
}
}
}
コントロールが更新されるまで本当に待つ必要がない限り、BeginInvoke
の代わりにInvoke
をできるだけ頻繁に使用します(この例ではそうではありません)。 BeginInvoke
は、WinFormsメッセージキューにデリゲートを投稿し、呼び出し元のコードをすぐに続行させます(この場合、SampleFunction
のforループ)。 Invoke
はデリゲートをポストするだけでなく、デリゲートが完了するまで待機します。
したがって、例のメソッドAppendTextBox
では、Invoke
をBeginInvoke
に置き換えます。
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
に関する小さなチュートリアルです。
コントロールを所有するスレッドからアクションを実行する必要があります。
それは私がコードノイズを追加しすぎることなくそれをやっている方法です:
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);
}
}
さらに簡単なのは、BackgroundWorkerコントロールを使用することです...
最も単純な、デリゲートを気にしない
if(textBox1.InvokeRequired == true)
textBox1.Invoke((MethodInvoker)delegate { textBox1.Text = "Invoke was needed";});
else
textBox1.Text = "Invoke was NOT needed";
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;
});
}
_
Control.BeginInvoke メソッドをご覧ください。ポイントは、別のスレッドからUIコントロールを更新しないことです。 BeginInvokeは、コントロールのUIスレッド(この場合はフォーム)に呼び出しをディスパッチします。
フォームを取得するには、サンプル関数から静的修飾子を削除し、MSDNの例に示すようにthis.BeginInvoke()を使用します。