次の方法で共有


方法: 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 つの方法を示しています。

  1. System.Windows.Forms.Control.Invoke メソッド。メイン スレッドからデリゲートを呼び出してコントロールを呼び出します。
  2. イベント ドリブン モデルを提供する System.ComponentModel.BackgroundWorker コンポーネント。

どちらの例でも、バックグラウンド スレッドは 1 秒間スリープ状態にして、そのスレッドで実行されている作業をシミュレートします。

これらの例は、C# または Visual Basic コマンド ラインから .NET Framework アプリとしてビルドして実行できます。 詳細については、コマンド ライン (Visual Basic)から csc.exe または Build を使用したコマンド ラインビルドの を参照してください。

.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クエリを実行します。 InvokeRequiredtrueを返す場合、WriteTextSafeSafeCallDelegateInvoke メソッドに渡して、コントロールを実際に呼び出します。 InvokeRequiredfalseを返す場合、WriteTextSafeTextBox.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.ProgressChangedBackgroundWorker.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

関連項目