방법: Windows Forms 컨트롤을 스레드로부터 안전 하 게 호출 합니다.How to: Make thread-safe calls to Windows Forms controls

다중 스레딩을 사용 하면 Windows Forms 앱의 성능을 향상 시킬 수 있지만 Windows Forms 컨트롤에 대 한 액세스는 기본적으로 스레드로부터 안전 하지 않습니다.Multithreading can improve the performance of Windows Forms apps, but access to Windows Forms controls isn't inherently thread-safe. 다중 스레딩을 통해 코드를 매우 심각 하 고 복잡 한 버그에 노출할 수 있습니다.Multithreading can expose your code to very serious and complex bugs. 컨트롤을 조작 하는 두 개 이상의 스레드가 컨트롤을 일관 되지 않은 상태로 강제 적용 하 여 경합 상태, 교착 상태, 중단 또는 중단을 초래할 수 있습니다.Two or more threads manipulating a control can force the control into an inconsistent state and lead to race conditions, deadlocks, and freezes or hangs. 응용 프로그램에서 다중 스레딩을 구현 하는 경우에는 스레드로부터 안전한 방식으로 크로스 스레드 컨트롤을 호출 해야 합니다.If you implement multithreading in your app, be sure to call cross-thread controls in a thread-safe way. 자세한 내용은 관리 되는 스레딩 모범 사례를 참조 하세요.For more information, see Managed threading best practices.

해당 컨트롤을 만들지 않은 스레드에서 Windows Forms 제어를 안전 하 게 호출 하는 방법에는 두 가지가 있습니다.There are two ways to safely call a Windows Forms control from a thread that didn't create that control. System.Windows.Forms.Control.Invoke 메서드를 사용 하 여 주 스레드에서 만든 대리자를 호출할 수 있습니다. 그러면이 대리자가 컨트롤을 호출 합니다.You can use the System.Windows.Forms.Control.Invoke method to call a delegate created in the main thread, which in turn calls the control. 또는 이벤트 기반 모델을 사용 System.ComponentModel.BackgroundWorker하 여 백그라운드 스레드에서 수행 된 작업을 결과에 대 한 보고에서 분리 하는를 구현할 수 있습니다.Or, you can implement a System.ComponentModel.BackgroundWorker, which uses an event-driven model to separate work done in the background thread from reporting on the results.

안전 하지 않은 크로스 스레드 호출Unsafe cross-thread calls

컨트롤을 만들지 않은 스레드에서 직접 호출 하는 것은 안전 하지 않습니다.It's unsafe to call a control directly from a thread that didn't create it. 다음 코드 조각에서는 System.Windows.Forms.TextBox 컨트롤에 대 한 안전 하지 않은 호출을 보여 줍니다.The following code snippet illustrates an unsafe call to the System.Windows.Forms.TextBox control. 이벤트 Button1_Click 처리기는 주 스레드의 TextBox.Text 속성 WriteTextUnsafe 을 직접 설정 하는 새 스레드를 만듭니다.The Button1_Click event handler creates a new WriteTextUnsafe thread, which sets the main thread's TextBox.Text property directly.

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 상태로를 발생 시켜 이러한 안전 하지 않은 스레드 호출을 검색 합니다. "" 컨트롤이 만들어진 스레드가 아닌 스레드에서 액세스 되었습니다.The Visual Studio debugger detects these unsafe thread calls by raising an InvalidOperationException with the message, Cross-thread operation not valid. Control "" accessed from a thread other than the thread it was created on. InvalidOperationException Visual Studio 디버깅 중 안전 하지 않은 크로스 스레드 호출에 대해 항상 발생 하며, 앱 런타임에서 발생할 수 있습니다.The InvalidOperationException always occurs for unsafe cross-thread calls during Visual Studio debugging, and may occur at app runtime. 문제를 해결 해야 하지만 Control.CheckForIllegalCrossThreadCalls 속성을로 false설정 하 여 예외를 비활성화할 수 있습니다.You should fix the issue, but you can disable the exception by setting the Control.CheckForIllegalCrossThreadCalls property to false.

안전한 크로스 스레드 호출Safe cross-thread calls

다음 코드 예제에서는 코드를 만들지 않은 스레드에서 Windows Forms 제어를 안전 하 게 호출 하는 두 가지 방법을 보여 줍니다.The following code examples demonstrate two ways to safely call a Windows Forms control from a thread that didn't create it:

  1. 컨트롤을 호출 하기 위해 주 스레드에서 대리자를 호출 하는 메서드입니다.System.Windows.Forms.Control.InvokeThe System.Windows.Forms.Control.Invoke method, which calls a delegate from the main thread to call the control.
  2. 이벤트 기반 모델을 제공 하는 구성요소입니다.System.ComponentModel.BackgroundWorkerA System.ComponentModel.BackgroundWorker component, which offers an event-driven model.

두 예제에서 백그라운드 스레드는 1 초 동안 대기 하 여 해당 스레드에서 수행 되는 작업을 시뮬레이션 합니다.In both examples, the background thread sleeps for one second to simulate work being done in that thread.

C# 또는 Visual Basic 명령줄에서 .NET Framework 앱으로 이러한 예제를 빌드하고 실행할 수 있습니다.You can build and run these examples as .NET Framework apps from the C# or Visual Basic command line. 자세한 내용은 csc.exe를 사용한 명령줄 빌드 또는 명령줄에서 빌드 (Visual Basic)를 참조 하세요.For more information, see Command-line building with csc.exe or Build from the command line (Visual Basic).

.Net core 3.0부터 .net core Windows Forms <폴더 이름 > .csproj 프로젝트 파일을 포함 하는 폴더에서 Windows .net core 앱으로 예제를 빌드하고 실행할 수도 있습니다.Starting with .NET Core 3.0, you can also build and run the examples as Windows .NET Core apps from a folder that has a .NET Core Windows Forms <folder name>.csproj project file.

예제: 대리자와 함께 Invoke 메서드 사용Example: Use the Invoke method with a delegate

다음 예제에서는 Windows Forms 컨트롤을 스레드로부터 안전 하 게 호출 하는 패턴을 보여 줍니다.The following example demonstrates a pattern for ensuring thread-safe calls to a Windows Forms control. 이 메서드는 System.Windows.Forms.Control.InvokeRequired 컨트롤의 스레드 id를 호출 하는 스레드 id와 비교 하는 속성을 쿼리 합니다.It queries the System.Windows.Forms.Control.InvokeRequired property, which compares the control's creating thread ID to the calling thread ID. 스레드 Id가 동일 하면 컨트롤을 직접 호출 합니다.If the thread IDs are the same, it calls the control directly. 스레드 id가 다른 경우에는 기본 스레드에서 대리자 Control.Invoke 를 사용 하 여 메서드를 호출 합니다. 그러면이 메서드는 컨트롤에 대 한 실제 호출을 수행 합니다.If the thread IDs are different, it calls the Control.Invoke method with a delegate from the main thread, which makes the actual call to the control.

SafeCallDelegate 컨트롤 TextBoxText 속성을 설정할 수 있습니다.The SafeCallDelegate enables setting the TextBox control's Text property. WriteTextSafe 메서드 쿼리입니다InvokeRequired.The WriteTextSafe method queries InvokeRequired. InvokeRequired true SafeCallDelegate 가 를반환하는경우를메서드에전달하여컨트롤에대한실제호출을수행합니다.WriteTextSafe InvokeIf InvokeRequired returns true, WriteTextSafe passes the SafeCallDelegate to the Invoke method to make the actual call to the control. InvokeRequiredfalse반환 WriteTextSafe 하면를 TextBox.Text 직접 설정 합니다.If InvokeRequired returns false, WriteTextSafe sets the TextBox.Text directly. 이벤트 처리기는 새 스레드를 만들고 메서드를 WriteTextSafe 실행 합니다. Button1_ClickThe Button1_Click event handler creates the new thread and runs the WriteTextSafe method.

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 이벤트 처리기 사용Example: Use a BackgroundWorker event handler

다중 스레딩을 구현 하는 쉬운 방법은 이벤트 구동 System.ComponentModel.BackgroundWorker 모델을 사용 하는 구성 요소를 사용 하는 것입니다.An easy way to implement multithreading is with the System.ComponentModel.BackgroundWorker component, which uses an event-driven model. 백그라운드 스레드는 주 스레드와 BackgroundWorker.DoWork 상호 작용 하지 않는 이벤트를 실행 합니다.The background thread runs the BackgroundWorker.DoWork event, which doesn't interact with the main thread. 주 스레드는 주 스레드의 BackgroundWorker.ProgressChanged 컨트롤 BackgroundWorker.RunWorkerCompleted 을 호출할 수 있는 및 이벤트 처리기를 실행 합니다.The main thread runs the BackgroundWorker.ProgressChanged and BackgroundWorker.RunWorkerCompleted event handlers, which can call the main thread's controls.

를 사용 BackgroundWorker하 여 스레드로부터 안전한 호출을 수행 하려면 백그라운드 스레드에서 작업을 수행 하는 메서드를 만들어 DoWork 이벤트에 바인딩합니다.To make a thread-safe call by using BackgroundWorker, create a method in the background thread to do the work, and bind it to the DoWork event. 주 스레드에 다른 메서드를 만들어 백그라운드 작업의 결과를 보고 ProgressChanged 하거나 또는 RunWorkerCompleted 이벤트에 바인딩합니다.Create another method in the main thread to report the results of the background work, and bind it to the ProgressChanged or RunWorkerCompleted event. 백그라운드 스레드를 시작 하려면를 호출 BackgroundWorker.RunWorkerAsync합니다.To start the background thread, call BackgroundWorker.RunWorkerAsync.

이 예제에서는 RunWorkerCompleted 이벤트 처리기를 사용 하 여 TextBox 컨트롤의 Text 속성을 설정 합니다.The example uses the RunWorkerCompleted event handler to set the TextBox control's Text property. 이벤트를 사용 하는 ProgressChanged 예제는를 BackgroundWorker참조 하십시오.For an example using the ProgressChanged event, see 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

참고자료See also