次の方法で共有


コントロールに対してスレッド セーフな呼び出しを行う方法

マルチスレッドは Windows フォーム アプリのパフォーマンスを向上させることができますが、Windows フォーム コントロールへのアクセスは本質的にスレッド セーフではありません。 マルチスレッドでは、コードが重大で複雑なバグにさらされる可能性があります。 コントロールを操作する 2 つ以上のスレッドによって、コントロールが不整合な状態になり、競合状態、デッドロック、フリーズまたはハングが発生する可能性があります。 アプリでマルチスレッドを実装する場合は、スレッド セーフな方法でクロススレッド コントロールを呼び出してください。 詳細については、「 マネージド スレッドのベスト プラクティス」を参照してください

そのコントロールを作成しなかったスレッドから Windows フォーム コントロールを安全に呼び出すには、2 つの方法があります。 System.Windows.Forms.Control.Invoke メソッドを使用して、メイン スレッドで作成されたデリゲートを呼び出し、次にコントロールを呼び出します。 または、 System.ComponentModel.BackgroundWorkerを実装します。イベント ドリブン モデルを使用して、バックグラウンド スレッドで行われた作業と結果に関するレポートを分離します。

安全でないスレッド間呼び出し

コントロールを作成しなかったスレッドから直接呼び出しても安全ではありません。 次のコード スニペットは、 System.Windows.Forms.TextBox コントロールへの安全でない呼び出しを示しています。 Button1_Click イベント ハンドラーは、メイン スレッドの WriteTextUnsafe プロパティを直接設定する新しいTextBox.Text スレッドを作成します。

private void button1_Click(object sender, EventArgs e)
{
    var thread2 = new System.Threading.Thread(WriteTextUnsafe);
    thread2.Start();
}

private void WriteTextUnsafe() =>
    textBox1.Text = "This text was set unsafely.";
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim thread2 As New System.Threading.Thread(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 秒間スリープ状態にして、そのスレッドで実行されている作業をシミュレートします。

例: Invoke メソッドを使用する

次の例は、Windows フォーム コントロールへのスレッド セーフな呼び出しを保証するパターンを示しています。 System.Windows.Forms.Control.InvokeRequired プロパティに対してクエリを実行します。このプロパティは、コントロールの作成スレッド ID と呼び出し元のスレッド ID を比較します。 異なる場合は、 Control.Invoke メソッドを呼び出す必要があります。

WriteTextSafeを使用すると、TextBox コントロールの Text プロパティを新しい値に設定できます。 メソッドは InvokeRequiredクエリを実行します。 InvokeRequiredtrueを返す場合は、WriteTextSafe自身を再帰的に呼び出し、メソッドをデリゲートとして Invoke メソッドに渡します。 InvokeRequiredfalseを返す場合、WriteTextSafeTextBox.Textを直接設定します。 Button1_Click イベント ハンドラーは、新しいスレッドを作成し、WriteTextSafe メソッドを実行します。

private void button1_Click(object sender, EventArgs e)
{
    var threadParameters = new System.Threading.ThreadStart(delegate { WriteTextSafe("This text was set safely."); });
    var thread2 = new System.Threading.Thread(threadParameters);
    thread2.Start();
}

public void WriteTextSafe(string text)
{
    if (textBox1.InvokeRequired)
    {
        // Call this same method but append THREAD2 to the text
        Action safeWrite = delegate { WriteTextSafe($"{text} (THREAD2)"); };
        textBox1.Invoke(safeWrite);
    }
    else
        textBox1.Text = text;
}
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

    Dim threadParameters As New System.Threading.ThreadStart(Sub()
                                                                 WriteTextSafe("This text was set safely.")
                                                             End Sub)

    Dim thread2 As New System.Threading.Thread(threadParameters)
    thread2.Start()

End Sub

Private Sub WriteTextSafe(text As String)

    If (TextBox1.InvokeRequired) Then

        TextBox1.Invoke(Sub()
                            WriteTextSafe($"{text} (THREAD2)")
                        End Sub)

    Else
        TextBox1.Text = text
    End If

End Sub

例: BackgroundWorker を使用する

マルチスレッドを実装する簡単な方法は、イベント ドリブン モデルを使用する System.ComponentModel.BackgroundWorker コンポーネントを使用することです。 バックグラウンド スレッドは、メイン スレッドと対話しない BackgroundWorker.DoWork イベントを発生させます。 メイン スレッドは、メイン スレッドのコントロールを呼び出すことができる BackgroundWorker.ProgressChangedBackgroundWorker.RunWorkerCompleted イベント ハンドラーを実行します。

BackgroundWorkerを使用してスレッド セーフな呼び出しを行うには、DoWork イベントを処理します。 バックグラウンド ワーカーが状態を報告するために使用するイベントには、 ProgressChangedRunWorkerCompletedの 2 つがあります。 ProgressChanged イベントは、状態の更新をメイン スレッドに伝達するために使用され、RunWorkerCompleted イベントは、バックグラウンド ワーカーがその作業を完了したことを通知するために使用されます。 バックグラウンド スレッドを開始するには、 BackgroundWorker.RunWorkerAsyncを呼び出します。

この例では、 DoWork イベントで 0 から 10 までの数をカウントし、カウントの間に 1 秒間一時停止します。 ProgressChanged イベント ハンドラーを使用して、メイン スレッドに数値を報告し、TextBox コントロールの Text プロパティを設定します。 ProgressChanged イベントを機能させるには、BackgroundWorker.WorkerReportsProgress プロパティを true に設定する必要があります。

private void button1_Click(object sender, EventArgs e)
{
    if (!backgroundWorker1.IsBusy)
        backgroundWorker1.RunWorkerAsync();
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    int counter = 0;
    int max = 10;

    while (counter <= max)
    {
        backgroundWorker1.ReportProgress(0, counter.ToString());
        System.Threading.Thread.Sleep(1000);
        counter++;
    }
}

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) =>
    textBox1.Text = (string)e.UserState;
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

    If (Not BackgroundWorker1.IsBusy) Then
        BackgroundWorker1.RunWorkerAsync()
    End If

End Sub

Private Sub BackgroundWorker1_DoWork(sender As Object, e As ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

    Dim counter = 0
    Dim max = 10

    While counter <= max

        BackgroundWorker1.ReportProgress(0, counter.ToString())
        System.Threading.Thread.Sleep(1000)

        counter += 1

    End While

End Sub

Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
    TextBox1.Text = e.UserState
End Sub