달력

2

« 2025/2 »

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

Window Forms 컨트롤의 다중 스레딩


https://msdn.microsoft.com/ko-kr/library/ms229730(v=vs.100).aspx


1. 응용 프로그램에서 시간이 많이 걸리는 작업(time-consuming operations)을 다른 스레드에서 수행하여 UI(사용자 인터페이스)의 응답 기능을 향상시킬 수 있는 경우가 많습니다.

2. System.Threading 네임스페이스, Control.BeginInvoke 메서드 및 BackgroundWorker 구성 요소(component)를 비롯한 수많은 도구(A number of tools)를 사용하여 Windows Forms 컨트롤을 다중 스레딩할 수 있습니다.


 참고 

1. BackgroundWorker는 System.Threading 네임스페이스와 Control.BeginInvoke 메서드를 대체하고 여기에 다른 기능을 추가하여 새로 도입된 구성 요소이지만 이전 버전과의 호환성(backward compatibility) 및 이후 사용 가능성을 고려하여 이러한 네임스페이스와 메서드를 계속 유지(retain)하도록 선택할 수 있습니다.

2. 자세한 내용은 BackgroundWorker 구성 요소 개요를 참조하십시오.


<단원 내용>

3. 방법: 스레드로부터 안전한 방식으로 Windows Forms 컨트롤 호출

    1) 스레드로부터 안전한 방식(thread-safe calls)으로 Windows Forms 컨트롤을 호출하는 방법을 보여 줍니다.

4. 방법: 백그라운드 스레드를 사용하여 파일 검색

    1) System.Threading 네임스페이스와 BeginInvoke 메서드를 사용하여 파일을 비동기적으로(asynchronously) 검색하는 방법을 보여 줍니다.

<참조>

5. BackgroundWorker

    1) 비동기 작업을 위해 작업자 스레드(a worker thread)를 캡슐화(encapsulate)하는 구성 요소를 설명합니다.

6. LoadAsync

    1) 소리를 비동기적으로 로드하는 방법을 설명합니다.

7. LoadAsync

    1) 이미지를 비동기적으로 로드하는 방법을 설명합니다.

<관련 단원>

8. 방법: 백그라운드에서 작업 실행

    1) BackgroundWorker 구성 요소를 사용하여 시간이 많이 걸리는 작업을 수행하는 방법을 보여 줍니다.

9. BackgroundWorker 구성 요소 개요

    1) BackgroundWorker 구성 요소를 비동기 작업에 사용하는 방법을 설명하는 항목(topics)을 제공합니다.





방법: 스레드로부터 안전한 방식으로 Windows Forms 컨트롤 호출


1. Windows Forms 응용 프로그램의 성능 향상을 위해 다중 스레딩을 사용할 경우 스레드로부터 안전한 방식으로 컨트롤을 호출해야 합니다.

2. Windows Forms 컨트롤에 대한 액세스는 기본적으로(inherently) 스레드로부터 안전하지 않습니다.

3. 컨트롤 상태(the control of a control)를 조작(manipulate)하는 스레드가 두 개 이상 있는 경우 컨트롤이 일관성 없는 상태(an inconsistent state)가 될 수 있습니다

4. 따라서 컨트롤에 대한 액세스가 스레드로부터 안전한 방식으로 수행되는지 확인해야 합니다.

5. Invoke 메서드를 사용하지 않고(other than) 컨트롤을 만든 스레드 이외의 스레드에서 컨트롤에 액세스하는 것은 안전하지 않습니다.

6. 다음은 스레드로부터 안전하지 않은 호출의 예제입니다.


// 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."; }


7. .NET Framework에서는 사용자가 스레드로부터 안전하지 않은 방식으로(in a way) 컨트롤에 액세스할 경우 이를 감지할(detect) 수 있습니다.

8. 디버거에서 응용 프로그램을 실행할 때 컨트롤을 만든 스레드가 아닌 스레드에서 해당 컨트롤을 호출하려고 하면 "컨트롤 이름 컨트롤이 자신이 만들어진 스레드가 아닌 스레드에서 액세스되었습니다."라는 메시지와 함께 InvalidOperationException이 발생합니다.

9. 이 예외는 일부 환경에서(under some circumstances) 런타임에 디버깅하는 동안 안정적으로(reliably) 발생합니다.

10. 이 예외는 .NET Framework 버전 2.0 이전의 .NET Framework에서 작성한 응용 프로그램을 디버깅할 때 발생할 수 있습니다.

11. 이 문제가 발견되면 해결하는 것이 좋지만(strongly advised to fix this problem) CheckForIllegalCrossThreadCalls 속성을 false로 설정하여 해당 기능을 비활성화할 수 있습니다.

12. 그러나 컨트롤이 Visual Studio .NET 2003 및 .NET Framework 1.1에서와 같은 방식으로 실행됩니다.


 참고 

 1. 폼에서 ActiveX 컨트롤을 사용하는 경우 디버거에서 실행할 때 크로스 스레드 InvalidOperationsException이 발생할 수 있습니다.

2. 이러한 경우에는 ActiveX 컨트롤에서 다중 스레딩을 지원하지 않습니다.

3. Windows Forms에서의 ActiveX 컨트롤 사용에 대한 자세한 내용은 Windows Forms 및 관리 되지 않는 응용 프로그램을 참조하십시오.

4. Visual Studio를 사용하는 경우에는 Visual Studio 호스팅 프로세스를 비활성화하여(disable) 이 예외가 발생하지 않게 할 수 있습니다.

5. 자세한 내용은 방법: 호스팅 프로세스 비활성화를 참조하십시오.


<스레드로부터 안전한 방식으로 Windows Forms 컨트롤호출>

13. 스레드로부터 안전한 방식으로 Windows Forms 컨트롤을 호출하려면

    1) 컨트롤의 InvokeRequired 속성을 쿼리합니다.

    2) InvokeRequired 가 true를 반환하는 경우에는 컨트롤을 실제로 호출하는 대리자를 사용하여 Invoke를 호출합니다

    3) InvokeRequired 가 false를 반환하는 경우에는 컨트롤을 직접 호출합니다.

14. 다음 코드 예제에서 스레드로부터 안전한 호출은 백그라운드 스레드에 의해 실행되는 ThreadProcSafe 메서드에서 구현(implemented)됩니다.

15. TextBox 컨트롤의 InvokeRequired가 true를 반환할 경우 ThreadProcSafe 메서드는 SetTextCallback 인스턴스를 만들어 폼의 Invoke 메서드에 전달합니다.

16. 그러면 Text 컨트롤을 만든 스레드에서 SetText 메서드가 호출되고 이 스레드 컨텍스트에서 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 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를 사용하여 스레드로부터 안전한 방식으로 호출>

17. 응용 프로그램에서 다중 스레딩을 구현하는 기본 방법은 BackgroundWorker 구성 요소를 사용하는 것입니다.

18. BackgroundWorker 구성 요소는 다중 스레딩의 이벤트 구동 모델(an event-driven model)을 사용합니다.

19. 백그라운드 스레드는 DoWork 이벤트 처리기를 실행하고, 컨트롤을 만드는 스레드는 ProgressChangedRunWorkerCompleted 이벤트 처리기를 실행합니다.

20. ProgressChangedRunWorkerCompleted 이벤트 처리기에서 컨트롤을 호출할 수 있습니다.

21. BackgroundWorker를 사용하여 스레드로부터 안전한 방식으로 호출하려면

    1) 백그라운드 스레드에서 수행하려는 작업을 수행하는 메서드를 만듭니다. 이 메서드에서 주 스레드로 만든 컨트롤을 호출하지 마십시오.

    2) 백그라운드 작업 완료 후 그 결과를 보고하는(report) 메서드를 만듭니다. 이 메서드에서 주 스레드로 만든 컨트롤을 호출할 수 있습니다.

    3) 1단계에서 만든 메서드를 BackgroundWorker 인스턴스의 DoWork 이벤트에 바인딩하고, 2단계에서 만든 메서드를 동일한 인스턴스의 RunWorkerCompleted 이벤트에 바인딩합니다.

    4) 백그라운드 스레드를 시작하려면 BackgroundWorker 인스턴스의 RunWorkerAsync 메서드를 호출합니다.

22. 다음 코드 예제에서는 DoWork 이벤트 처리기에서 Sleep을 사용하여 시간이 많이 소요되는(takes some time) 작업을 시뮬레이션합니다.

23. 폼의 TextBox 컨트롤은 호출되지 않습니다.

24. 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.";
		}


25 ProgressChanged 이벤트를 사용하여 백그라운드 작업의 진행률을 보고할 수도 있습니다. 이 이벤트를 포함하는 예제는 BackgroundWorker를 참조하십시오.

26 다음 코드 예제는 세 개의 단추와 한 개의 텍스트 상자가 있는 폼으로 구성되는(consists of) 완전한 Windows Forms 응용 프로그램입니다.

27. 첫 번째 단추는 안전하지 않은 크로스 스레드 액세스를 보여 주고, 두 번째 단추는 Invoke를 사용하여 안전한 액세스를 보여 주며, 세 번째 단추는 BackgroundWorker를 사용하여 안전한 액세스를 보여 줍니다.


 참고 

1. 예제를 실행하는 방법에 대한 자세한 내용(instructions)은 방법: Visual Studio를 사용하여 전체 Windows Forms 코드 예제 컴파일 및 실행을 참조하십시오.

2. 이 예제에는 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());
		}

	}
}


28. 응용 프로그램을 실행하고 Unsafe Call 단추를 클릭하면 텍스트 상자에 "Written by the main thread"가 바로(immediately) 표시됩니다.

29. 2초 후(Two seconds later), 안전하지 않은 호출이 시도되면 Visual Studio 디버거에서 예외가 발생했음을 나타냅니다.

30. 텍스트 상자에 직접 쓰려고 한 백그라운드 스레드의 줄에서 디버거가 중지합니다.

31. 다른 두 단추를 테스트하기 위해 응용 프로그램을 다시 시작해야 합니다.

32. Safe Call 단추를 클릭하면 텍스트 상자에 "Written by the main thread"가 표시됩니다.

33. 2초 후 텍스트 상자가 "Written by the background thread (Invoke)"로 설정되어 Invoke 메서드가 호출되었음을 나타냅니다.

34. Safe BW Call 단추를 클릭하면 텍스트 상자에 "Written by the main thread"가 표시됩니다.

35. 2초 후 텍스트 상자가 "Written by the main thread after the background thread completed"로 설정되어 BackgroundWorkerRunWorkerCompleted 이벤트에 대한 처리기가 호출되었음을 나타냅니다.

<강력한 프로그래밍>

 주의 

1. 어떤 종류의 다중 스레딩을 사용하든지 코드가 매우 심각(serious)하고 복잡한(complex) 버그에 노출될 수 있습니다

2. 다중 스레딩을 사용하는 솔루션을 구현하기 전에 관리되는 스레딩을 구현하는 최선의 방법에서 자세한 내용을 참조하십시오.








'프로그래밍 > C#' 카테고리의 다른 글

String.Format 메서드  (0) 2016.06.28
String 클래스  (0) 2016.06.22
스레드  (0) 2016.06.14
보간된 문자열(Interpolated strings)  (0) 2016.06.14
제네릭  (0) 2016.06.11
:
Posted by 지훈2