Form이 로드 될 때, 아래 한 줄 추가
CheckForIllegalCrossThreadCalls = false;
private void Form1_Load(object sender, EventArgs e)
{
CheckForIllegalCrossThreadCalls = false;
}
방법: 스레드로부터 안전한 방식으로 Windows Forms 컨트롤 호출
게시 날짜: 2016년 11월
Windows Forms 응용 프로그램의 성능을 개선하기 위해 다중 스레딩을 사용하는 경우에는 스레드로부터 안전한 방식으로 컨트롤을 호출할 수 있습니다.
Windows Forms 컨트롤에 대한 액세스는 기본적으로 스레드로부터 안전하지 않습니다. 둘 이상의 스레드가 컨트롤 상태를 조작하는 경우 컨트롤이 불일치하는 상태로 강제 지정될 수 있습니다. 또한 경합 상태와 교착 상태 등의 기타 스레드 관련 버그도 발생할 수 있습니다. 컨트롤에 액세스할 때는 스레드로부터 안전한 방식을 사용해야 합니다.
Invoke 메서드를 사용하지 않고 컨트롤을 만든 스레드가 아닌 다른 스레드에서 컨트롤을 호출하는 것은 안전하지 않습니다. 아래에는 스레드로부터 안전하지 않은 호출의 예제가 나와 있습니다.
// This event handler creates a thread that calls a // Windows Forms control in an unsafe way. private void setTextUnsafeBtn_Click( object sender, EventArgs e) { this.demoThread = new Thread(new ThreadStart(this.ThreadProcUnsafe)); this.demoThread.Start(); } // This method is executed on the worker thread and makes // an unsafe call on the TextBox control. private void ThreadProcUnsafe() { this.textBox1.Text = "This text was set unsafely."; }
.NET Framework에서는 스레드로부터 안전하지 않은 방식으로 컨트롤에 액세스하는 경우를 검색할 수 있습니다. 디버거에서 응용 프로그램을 실행할 때 컨트롤을 만든 스레드가 아닌 다른 스레드가 해당 컨트롤을 호출하려고 하면 디버거에서 InvalidOperationException이 발생하고 "컨트롤 이름 컨트롤이 자신이 만들어진 스레드가 아닌 스레드에서 액세스되었습니다." 메시지가 표시됩니다.
이 예외는 안전하지 않은 액세스를 안정적으로 확인할 수 있도록 디버깅 중에 발생하며 가끔 런타임에 발생하는 경우도 있습니다..NET Framework 이전 .NET Framework 2.0 버전을 사용하여 작성한 응용 프로그램을 디버그할 때 이 예외가 표시될 수 있습니다. 이 문제는 표시되는 경우 해결하는 것이 좋지만 CheckForIllegalCrossThreadCalls 속성을 false
로 설정하면 비활성화할 수 있습니다. 이렇게 하면 컨트롤이 Visual Studio.NET 2003 및 .NET Framework 1.1에서 실행되는 것처럼 실행됩니다.
|
---|
폼에서 ActiveX 컨트롤을 사용하는 경우 디버거에서 실행 시 크로스 스레드 InvalidOperationException이 발생할 수 있습니다. 이 예외가 발생하면 ActiveX 컨트롤이 다중 스레딩을 지원하지 않습니다. Windows Forms에서 ActiveX 컨트롤을 사용하는 방법에 대한 자세한 내용은 Windows Forms 및 관리되지 않는 응용 프로그램를 참조하세요. Visual Studio를 사용하는 경우 Visual Studio 호스팅 프로세스를 사용하지 않도록 설정하여 이 예외를 방지할 수 있습니다(방법: 호스팅 프로세스 비활성화 참조). |
스레드로부터 안전한 방식으로 Windows Forms 컨트롤을 호출하려면
컨트롤의 InvokeRequired 속성을 쿼리합니다.
InvokeRequired가
true
를 반환하면 실제 컨트롤 호출을 수행하는 대리자를 포함하여 Invoke를 호출합니다.InvokeRequired가
false
를 반환하면 컨트롤을 직접 호출합니다.
다음 코드 예제에서는 백그라운드 스레드를 통해 실행되는 ThreadProcSafe
메서드에서 스레드로부터 안전한 호출이 구현됩니다.TextBox 컨트롤의 InvokeRequired가 true
를 반환하면 ThreadProcSafe
메서드는 SetTextCallback
인스턴스를 만들어 폼의 Invoke 메서드로 전달합니다. 이 경우 SetText
컨트롤을 만든 스레드에서 TextBox 메서드가 호출되며 이 스레드 컨텍스트에서 Text 속성이 직접 설정됩니다.
// This event handler creates a thread that calls a // Windows Forms control in a thread-safe way. private void setTextSafeBtn_Click( object sender, EventArgs e) { this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe)); this.demoThread.Start(); } // This method is executed on the worker thread and makes // a thread-safe call on the TextBox control. private void ThreadProcSafe() { this.SetText("This text was set safely."); }
// This delegate enables asynchronous calls for setting // the text property on a TextBox control. delegate void SetTextCallback(string text);
// This method demonstrates a pattern for making thread-safe // calls on a Windows Forms control. // // If the calling thread is different from the thread that // created the TextBox control, this method creates a // SetTextCallback and calls itself asynchronously using the // Invoke method. // // If the calling thread is the same as the thread that created // the TextBox control, the Text property is set directly. private void SetText(string text) { // InvokeRequired required compares the thread ID of the // calling thread to the thread ID of the creating thread. // If these threads are different, it returns true. if (this.textBox1.InvokeRequired) { SetTextCallback d = new SetTextCallback(SetText); this.Invoke(d, new object[] { text }); } else { this.textBox1.Text = text; } }
응용 프로그램에서 다중 스레딩을 구현하는 기본 방법은 BackgroundWorker 구성 요소를 사용하는 것입니다.BackgroundWorker 구성 요소는 다중 스레딩에 이벤트 구동 모델을 사용합니다. 백그라운드 스레드는 DoWork 이벤트 처리기를 실행하며 컨트롤을 만드는 스레드는 ProgressChanged 및 RunWorkerCompleted 이벤트 처리기를 실행합니다.ProgressChanged 및 RunWorkerCompleted 이벤트 처리기에서 컨트롤을 호출할 수 있습니다.
BackgroundWorker를 사용하여 스레드로부터 안전한 호출을 수행하려면
백그라운드 스레드에서 수행하려는 작업을 수행하는 메서드를 만듭니다. 이 메서드에서 Main 메서드가 만든 컨트롤을 호출하지 마세요.
백그라운드 작업이 완료된 후 해당 작업의 결과를 보고하는 메서드를 만듭니다. 이 메서드에서는 Main 메서드가 만든 컨트롤을 호출할 수 있습니다.
1단계에서 만든 메서드를 DoWork 인스턴스의 BackgroundWorker 이벤트에 바인딩하고 2단계에서 만든 메서드를 같은 인스턴스의 RunWorkerCompleted 이벤트에 바인딩합니다.
백그라운드 스레드를 시작하려면 RunWorkerAsync 인스턴스의 BackgroundWorker 메서드를 호출합니다.
다음 코드 예제에서 DoWork 이벤트 처리기는 Sleep를 사용하여 시간이 오래 걸리는 작업을 시뮬레이트합니다. 그러나 폼의 TextBox 컨트롤은 호출하지 않습니다.TextBox 컨트롤의 Text 속성은 RunWorkerCompleted 이벤트 처리기에서 직접 설정됩니다.
// This BackgroundWorker is used to demonstrate the // preferred way of performing asynchronous operations. private BackgroundWorker backgroundWorker1;
// This event handler starts the form's // BackgroundWorker by calling RunWorkerAsync. // // The Text property of the TextBox control is set // when the BackgroundWorker raises the RunWorkerCompleted // event. private void setTextBackgroundWorkerBtn_Click( object sender, EventArgs e) { this.backgroundWorker1.RunWorkerAsync(); } // This event handler sets the Text property of the TextBox // control. It is called on the thread that created the // TextBox control, so the call is thread-safe. // // BackgroundWorker is the preferred way to perform asynchronous // operations. private void backgroundWorker1_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e) { this.textBox1.Text = "This text was set safely by BackgroundWorker."; }
ProgressChanged 이벤트를 사용하여 백그라운드 작업의 진행률을 보고할 수도 있습니다. 해당 이벤트가 통합되어 있는 예제를 보려면 BackgroundWorker를 참조하세요.
다음 코드 예제는 단추 세 개와 텍스트 상자 하나가 포함된 폼으로 구성되는 완전한 Windows Forms 응용 프로그램입니다. 첫 번째 단추는 안전하지 않은 크로스 스레드 액세스를, 두 번째 단추는 Invoke를 사용하는 안전한 액세스를, 세 번째 단추는 BackgroundWorker를 사용하는 안전한 액세스를 보여 줍니다.
|
---|
예제를 실행하는 방법에 대한 지침은 How to: Compile and Run a Complete Windows Forms Code Example Using Visual Studio를 참조하세요. 이 예제를 실행하려면 System.Drawing 및 System.Windows.Forms 어셈블리에 대한 참조가 필요합니다. |
using System; using System.ComponentModel; using System.Threading; using System.Windows.Forms; namespace CrossThreadDemo { public class Form1 : Form { // This delegate enables asynchronous calls for setting // the text property on a TextBox control. delegate void SetTextCallback(string text); // This thread is used to demonstrate both thread-safe and // unsafe ways to call a Windows Forms control. private Thread demoThread = null; // This BackgroundWorker is used to demonstrate the // preferred way of performing asynchronous operations. private BackgroundWorker backgroundWorker1; private TextBox textBox1; private Button setTextUnsafeBtn; private Button setTextSafeBtn; private Button setTextBackgroundWorkerBtn; private System.ComponentModel.IContainer components = null; public Form1() { InitializeComponent(); } protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } // This event handler creates a thread that calls a // Windows Forms control in an unsafe way. private void setTextUnsafeBtn_Click( object sender, EventArgs e) { this.demoThread = new Thread(new ThreadStart(this.ThreadProcUnsafe)); this.demoThread.Start(); } // This method is executed on the worker thread and makes // an unsafe call on the TextBox control. private void ThreadProcUnsafe() { this.textBox1.Text = "This text was set unsafely."; } // This event handler creates a thread that calls a // Windows Forms control in a thread-safe way. private void setTextSafeBtn_Click( object sender, EventArgs e) { this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe)); this.demoThread.Start(); } // This method is executed on the worker thread and makes // a thread-safe call on the TextBox control. private void ThreadProcSafe() { this.SetText("This text was set safely."); } // This method demonstrates a pattern for making thread-safe // calls on a Windows Forms control. // // If the calling thread is different from the thread that // created the TextBox control, this method creates a // SetTextCallback and calls itself asynchronously using the // Invoke method. // // If the calling thread is the same as the thread that created // the TextBox control, the Text property is set directly. private void SetText(string text) { // InvokeRequired required compares the thread ID of the // calling thread to the thread ID of the creating thread. // If these threads are different, it returns true. if (this.textBox1.InvokeRequired) { SetTextCallback d = new SetTextCallback(SetText); this.Invoke(d, new object[] { text }); } else { this.textBox1.Text = text; } } // This event handler starts the form's // BackgroundWorker by calling RunWorkerAsync. // // The Text property of the TextBox control is set // when the BackgroundWorker raises the RunWorkerCompleted // event. private void setTextBackgroundWorkerBtn_Click( object sender, EventArgs e) { this.backgroundWorker1.RunWorkerAsync(); } // This event handler sets the Text property of the TextBox // control. It is called on the thread that created the // TextBox control, so the call is thread-safe. // // BackgroundWorker is the preferred way to perform asynchronous // operations. private void backgroundWorker1_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e) { this.textBox1.Text = "This text was set safely by BackgroundWorker."; } #region Windows Form Designer generated code private void InitializeComponent() { this.textBox1 = new System.Windows.Forms.TextBox(); this.setTextUnsafeBtn = new System.Windows.Forms.Button(); this.setTextSafeBtn = new System.Windows.Forms.Button(); this.setTextBackgroundWorkerBtn = new System.Windows.Forms.Button(); this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker(); this.SuspendLayout(); // // textBox1 // this.textBox1.Location = new System.Drawing.Point(12, 12); this.textBox1.Name = "textBox1"; this.textBox1.Size = new System.Drawing.Size(240, 20); this.textBox1.TabIndex = 0; // // setTextUnsafeBtn // this.setTextUnsafeBtn.Location = new System.Drawing.Point(15, 55); this.setTextUnsafeBtn.Name = "setTextUnsafeBtn"; this.setTextUnsafeBtn.TabIndex = 1; this.setTextUnsafeBtn.Text = "Unsafe Call"; this.setTextUnsafeBtn.Click += new System.EventHandler(this.setTextUnsafeBtn_Click); // // setTextSafeBtn // this.setTextSafeBtn.Location = new System.Drawing.Point(96, 55); this.setTextSafeBtn.Name = "setTextSafeBtn"; this.setTextSafeBtn.TabIndex = 2; this.setTextSafeBtn.Text = "Safe Call"; this.setTextSafeBtn.Click += new System.EventHandler(this.setTextSafeBtn_Click); // // setTextBackgroundWorkerBtn // this.setTextBackgroundWorkerBtn.Location = new System.Drawing.Point(177, 55); this.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn"; this.setTextBackgroundWorkerBtn.TabIndex = 3; this.setTextBackgroundWorkerBtn.Text = "Safe BW Call"; this.setTextBackgroundWorkerBtn.Click += new System.EventHandler(this.setTextBackgroundWorkerBtn_Click); // // backgroundWorker1 // this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted); // // Form1 // this.ClientSize = new System.Drawing.Size(268, 96); this.Controls.Add(this.setTextBackgroundWorkerBtn); this.Controls.Add(this.setTextSafeBtn); this.Controls.Add(this.setTextUnsafeBtn); this.Controls.Add(this.textBox1); this.Name = "Form1"; this.Text = "Form1"; this.ResumeLayout(false); this.PerformLayout(); } #endregion [STAThread] static void Main() { Application.EnableVisualStyles(); Application.Run(new Form1()); } } }
응용 프로그램을 실행하고 Unsafe Call 단추를 클릭하는 즉시 텍스트 상자에 "Written by the main thread"가 표시됩니다. 그리고 2초 후에 안전하지 않은 호출을 시도하면 Visual Studio 디버거에 예외가 발생했음이 표시됩니다. 그러면 디버거가 텍스트 상자에 직접 쓰기를 시도한 백그라운드 스레드의 줄에서 중지됩니다. 나머지 두 단추를 테스트하려면 응용 프로그램을 다시 시작해야 합니다.Safe Call 단추를 클릭하면 텍스트 상자에 "Written by the main thread"가 표시되고, 2초 후에 텍스트 상자는 Invoke 메서드가 호출되었음을 나타내는 "Written by the background thread (Invoke)"로 설정됩니다.Safe BW Call 단추를 클릭하면 텍스트 상자에 "Written by the main thread"가 표시되고, 2초 후에 텍스트 상자는 RunWorkerCompleted의 BackgroundWorker 이벤트에 대한 처리기가 호출되었음을 나타내는 "Written by the main thread after the background thread completed"가 표시됩니다.
출처 : https://msdn.microsoft.com/ko-kr/library/ms171728