方法: Windows フォーム コントロールに対してスレッド セーフな呼び出しを行う
マルチスレッドは Windows フォーム アプリのパフォーマンスを向上させることができますが、Windows フォーム コントロールへのアクセスは本質的にスレッド セーフではありません。 マルチスレッドでは、コードが非常に深刻で複雑なバグにさらされる可能性があります。 コントロールを操作する 2 つ以上のスレッドによって、コントロールが不整合な状態になり、競合状態、デッドロック、フリーズまたはハングが発生する可能性があります。 アプリでマルチスレッドを実装する場合は、スレッド セーフな方法でクロススレッド コントロールを呼び出してください。 詳細については、「マネージド スレッドのベスト プラクティス
そのコントロールを作成しなかったスレッドから Windows フォーム コントロールを安全に呼び出すには、2 つの方法があります。 System.Windows.Forms.Control.Invoke メソッドを使用して、メイン スレッドで作成されたデリゲートを呼び出し、次にコントロールを呼び出すことができます。 または、System.ComponentModel.BackgroundWorkerを実装できます。イベント ドリブン モデルを使用して、バックグラウンド スレッドで行われた作業と結果に関するレポートを分離します。
安全でないスレッド間呼び出し
コントロールを作成しなかったスレッドから直接呼び出しても安全ではありません。 次のコード スニペットは、System.Windows.Forms.TextBox コントロールへの安全でない呼び出しを示しています。 Button1_Click
イベント ハンドラーは、メイン スレッドの TextBox.Text プロパティを直接設定する新しい WriteTextUnsafe
スレッドを作成します。
private void Button1_Click(object sender, EventArgs e)
{
thread2 = new Thread(new ThreadStart(WriteTextUnsafe));
thread2.Start();
}
private void WriteTextUnsafe()
{
textBox1.Text = "This text was set unsafely.";
}
Private Sub Button1_Click(ByVal sender As Object, e As EventArgs) Handles Button1.Click
Thread2 = New Thread(New ThreadStart(AddressOf WriteTextUnsafe))
Thread2.Start()
End Sub
Private Sub WriteTextUnsafe()
TextBox1.Text = "This text was set unsafely."
End Sub
Visual Studio デバッガーは、メッセージを含む InvalidOperationException を発生させることで、これらの安全でないスレッド呼び出しを検出 、スレッド間操作は無効です。作成されたスレッド以外のスレッドからアクセスされる "" を制御します。InvalidOperationException は、Visual Studio のデバッグ中に安全でないスレッド間呼び出しに対して常に発生し、アプリの実行時に発生する可能性があります。 この問題は修正する必要がありますが、Control.CheckForIllegalCrossThreadCalls プロパティを false
に設定することで、例外を無効にできます。
安全なスレッド間呼び出し
次のコード例は、Windows フォーム コントロールを作成しなかったスレッドから安全に呼び出す 2 つの方法を示しています。
- System.Windows.Forms.Control.Invoke メソッド。メイン スレッドからデリゲートを呼び出してコントロールを呼び出します。
- イベント ドリブン モデルを提供する System.ComponentModel.BackgroundWorker コンポーネント。
どちらの例でも、バックグラウンド スレッドは 1 秒間スリープ状態にして、そのスレッドで実行されている作業をシミュレートします。
これらの例は、C# または Visual Basic コマンド ラインから .NET Framework アプリとしてビルドして実行できます。 詳細については、コマンド ライン (Visual Basic)から csc.exe または
.NET Core 3.0以降では、.NET Core Windows フォーム <フォルダー名のフォルダーから、.csproj プロジェクトファイルを使用して、Windows .NET Coreアプリとして例>をビルドおよび実行することができます。
例: デリゲートで Invoke メソッドを使用する
次の例は、Windows フォーム コントロールへのスレッド セーフな呼び出しを保証するパターンを示しています。 System.Windows.Forms.Control.InvokeRequired プロパティに対してクエリを実行します。このプロパティは、コントロールの作成スレッド ID と呼び出し元のスレッド ID を比較します。 スレッド ID が同じ場合は、コントロールを直接呼び出します。 スレッド ID が異なる場合は、メイン スレッドのデリゲートを使用して Control.Invoke メソッドを呼び出します。これにより、コントロールへの実際の呼び出しが行われます。
SafeCallDelegate
を使用すると、TextBox コントロールの Text プロパティを設定できます。 WriteTextSafe
メソッドは、InvokeRequiredクエリを実行します。 InvokeRequired が true
を返す場合、WriteTextSafe
は SafeCallDelegate
を Invoke メソッドに渡して、コントロールを実際に呼び出します。 InvokeRequired が false
を返す場合、WriteTextSafe
TextBox.Text を直接設定します。 Button1_Click
イベント ハンドラーは、新しいスレッドを作成し、WriteTextSafe
メソッドを実行します。
using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
public class InvokeThreadSafeForm : Form
{
private delegate void SafeCallDelegate(string text);
private Button button1;
private TextBox textBox1;
private Thread thread2 = null;
[STAThread]
static void Main()
{
Application.SetCompatibleTextRenderingDefault(false);
Application.EnableVisualStyles();
Application.Run(new InvokeThreadSafeForm());
}
public InvokeThreadSafeForm()
{
button1 = new Button
{
Location = new Point(15, 55),
Size = new Size(240, 20),
Text = "Set text safely"
};
button1.Click += new EventHandler(Button1_Click);
textBox1 = new TextBox
{
Location = new Point(15, 15),
Size = new Size(240, 20)
};
Controls.Add(button1);
Controls.Add(textBox1);
}
private void Button1_Click(object sender, EventArgs e)
{
thread2 = new Thread(new ThreadStart(SetText));
thread2.Start();
Thread.Sleep(1000);
}
private void WriteTextSafe(string text)
{
if (textBox1.InvokeRequired)
{
var d = new SafeCallDelegate(WriteTextSafe);
textBox1.Invoke(d, new object[] { text });
}
else
{
textBox1.Text = text;
}
}
private void SetText()
{
WriteTextSafe("This text was set safely.");
}
}
Imports System.Drawing
Imports System.Threading
Imports System.Windows.Forms
Public Class InvokeThreadSafeForm : Inherits Form
Public Shared Sub Main()
Application.SetCompatibleTextRenderingDefault(False)
Application.EnableVisualStyles()
Dim frm As New InvokeThreadSafeForm()
Application.Run(frm)
End Sub
Dim WithEvents Button1 As Button
Dim TextBox1 As TextBox
Dim Thread2 as Thread = Nothing
Delegate Sub SafeCallDelegate(text As String)
Private Sub New()
Button1 = New Button()
With Button1
.Location = New Point(15, 55)
.Size = New Size(240, 20)
.Text = "Set text safely"
End With
TextBox1 = New TextBox()
With TextBox1
.Location = New Point(15, 15)
.Size = New Size(240, 20)
End With
Controls.Add(Button1)
Controls.Add(TextBox1)
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Thread2 = New Thread(New ThreadStart(AddressOf SetText))
Thread2.Start()
Thread.Sleep(1000)
End Sub
Private Sub WriteTextSafe(text As String)
If TextBox1.InvokeRequired Then
Dim d As New SafeCallDelegate(AddressOf SetText)
TextBox1.Invoke(d, New Object() {text})
Else
TextBox1.Text = text
End If
End Sub
Private Sub SetText()
WriteTextSafe("This text was set safely.")
End Sub
End Class
例: BackgroundWorker イベント ハンドラーを使用する
マルチスレッドを実装する簡単な方法は、イベント ドリブン モデルを使用する System.ComponentModel.BackgroundWorker コンポーネントを使用することです。 バックグラウンド スレッドは、メイン スレッドと対話しない BackgroundWorker.DoWork イベントを実行します。 メイン スレッドは、メイン スレッドのコントロールを呼び出すことができる BackgroundWorker.ProgressChanged と BackgroundWorker.RunWorkerCompleted イベント ハンドラーを実行します。
BackgroundWorkerを使用してスレッド セーフな呼び出しを行うには、バックグラウンド スレッドで処理を行うメソッドを作成し、DoWork イベントにバインドします。 メイン スレッドに別のメソッドを作成してバックグラウンド処理の結果を報告し、ProgressChanged または RunWorkerCompleted イベントにバインドします。 バックグラウンド スレッドを開始するには、BackgroundWorker.RunWorkerAsyncを呼び出します。
この例では、RunWorkerCompleted イベント ハンドラーを使用して、TextBox コントロールの Text プロパティを設定します。 ProgressChanged イベントの使用例については、BackgroundWorkerを参照してください。
using System;
using System.ComponentModel;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
public class BackgroundWorkerForm : Form
{
private BackgroundWorker backgroundWorker1;
private Button button1;
private TextBox textBox1;
[STAThread]
static void Main()
{
Application.SetCompatibleTextRenderingDefault(false);
Application.EnableVisualStyles();
Application.Run(new BackgroundWorkerForm());
}
public BackgroundWorkerForm()
{
backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.DoWork += new DoWorkEventHandler(BackgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorker1_RunWorkerCompleted);
button1 = new Button
{
Location = new Point(15, 55),
Size = new Size(240, 20),
Text = "Set text safely with BackgroundWorker"
};
button1.Click += new EventHandler(Button1_Click);
textBox1 = new TextBox
{
Location = new Point(15, 15),
Size = new Size(240, 20)
};
Controls.Add(button1);
Controls.Add(textBox1);
}
private void Button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Sleep 2 seconds to emulate getting data.
Thread.Sleep(2000);
e.Result = "This text was set safely by BackgroundWorker.";
}
private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
textBox1.Text = e.Result.ToString();
}
}
Imports System.ComponentModel
Imports System.Drawing
Imports System.Threading
Imports System.Windows.Forms
Public Class BackgroundWorkerForm : Inherits Form
Public Shared Sub Main()
Application.SetCompatibleTextRenderingDefault(False)
Application.EnableVisualStyles()
Dim frm As New BackgroundWorkerForm()
Application.Run(frm)
End Sub
Dim WithEvents BackgroundWorker1 As BackgroundWorker
Dim WithEvents Button1 As Button
Dim TextBox1 As TextBox
Private Sub New()
BackgroundWorker1 = New BackgroundWorker()
Button1 = New Button()
With Button1
.Text = "Set text safely with BackgroundWorker"
.Location = New Point(15, 55)
.Size = New Size(240, 20)
End With
TextBox1 = New TextBox()
With TextBox1
.Location = New Point(15, 15)
.Size = New Size(240, 20)
End With
Controls.Add(Button1)
Controls.Add(TextBox1)
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) _
Handles BackgroundWorker1.DoWork
' Sleep 2 seconds to emulate getting data.
Thread.Sleep(2000)
e.Result = "This text was set safely by BackgroundWorker."
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) _
Handles BackgroundWorker1.RunWorkerCompleted
textBox1.Text = e.Result.ToString()
End Sub
End Class
関連項目
- BackgroundWorker
- 方法: バックグラウンド で操作を実行する
- 方法: バックグラウンド操作を使用するフォームを実装する
- .NET Framework を使用してカスタム Windows フォーム コントロールを開発する
.NET Desktop feedback