NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# 31. Delegate와 Cross Thread. (대리자와 크로스 스레드)

    페이지 정보

    본문

    안녕하세요. 소심비형입니다. .NET 1.1 Framework에서는 좀처럼 보기 힘들었던 크로스 스레드 문제가 Visual Studio 2005부터 자주 보게 되었는데요. 아래 속성을 false로 설정하면 .NET 1.1 Framework처럼 동작합니다. 이는 디버깅을 위한 임시 방편이니 이대로 배포하면 안됩니다-_-;

            public Form1()
            {
                InitializeComponent();
                CheckForIllegalCrossThreadCalls = false;
            }
    

     

     

    멀티 스레딩에 대한 내용은 나중에 자세히 알아보기로 하고, 델리게이트를 알아보기 위해 크로스 스레딩을 발생시키고 어떻게 처리되는지 알아보겠습니다. 아래 그림처럼 화면을 디자인 하세요. 이 응용 프로그램은 사용자가 지정한 폴더에서 발생되는 변화(폴더 또는 파일이 생기거나 변경, 삭제)를 감시하고, 변화가 감지되면 로그를 남깁니다.

     

    .NET은 폴더 또는 파일의 변화를 감시할 수 있는 FileSystemWatcher 클래스를 제공하고 있습니다. 이 클래스는 내부적으로 자체 스레드를 사용하여 변화를 감시합니다.

    Form1.cs

    using System;
    using System.IO;
    using System.Windows.Forms;
    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            private FileSystemWatcher fileWatcher = new FileSystemWatcher();
     
            public Form1()
            {
                InitializeComponent();
                fileWatcher.Created += new FileSystemEventHandler(fileWatcher_Event);
                fileWatcher.Changed += new FileSystemEventHandler(fileWatcher_Event);
                fileWatcher.Renamed += new RenamedEventHandler(fileWatcher_Event);
                fileWatcher.Deleted += new FileSystemEventHandler(fileWatcher_Event);
            }
     
            private void btnFolder_Click(object sender, EventArgs e)
            {
                if (folderBrowserDialog1.ShowDialog(this) == System.Windows.Forms.DialogResult.OK)
                {
                    txtRoot.Text = folderBrowserDialog1.SelectedPath;
                }
            }
     
            private void btnRun_Click(object sender, EventArgs e)
            {
                fileWatcher.Path = txtRoot.Text;
                fileWatcher.IncludeSubdirectories = chkSubdirectories.Checked;
                fileWatcher.EnableRaisingEvents = true;
            }
     
            private void fileWatcher_Event(object sender, FileSystemEventArgs e)
            {
                this.Invoke((MethodInvoker)delegate
                { txtLog.Text += string.Format("[{0}] {1}{2}", e.ChangeType, e.FullPath, Environment.NewLine); });
            }
     
            private void btnStop_Click(object sender, EventArgs e)
            {
                fileWatcher.EnableRaisingEvents = false;
            }
        }
    }

     

     

    위 코드에서 40번째 라인만 남겨두고 Invoke를 제거하면 아래와 같은 에러를 발생시키게 됩니다.

    • 처리되지 않은 'System.InvalidOperationException' 형식의 예외가 System.Windows.Forms.dll에서 발생했습니다.
    • 추가 정보: 크로스 스레드 작업이 잘못되었습니다. 'txtLog' 컨트롤이 자신이 만들어진 스레드가 아닌 스레드에서 액세스되었습니다.

     

     

    이 오류가 발생되는 이유는 간단합니다. WinForm 또는 WPF로 만드는 모든 응용 프로그램은 하나의 Process에 하나의 Thread로 구성됩니다. 그리고 그 Thread(Main Thread 또는 UI Thread)가 화면을 그리고 컨트롤들을 관리하죠. 하지만, 폼을 포함한 컨트롤들은 기본적으로 다른 스레드로부터 안전하지 않습니다. 이 말은 "다른 스레드가 컨트롤에 접근하는 것을 막지 않는다!"는 뜻입니다. 명시적으로 다른 스레드를 막으려면 lock을 이용합니다.

    (Form도 Control에서 파생된 컨트롤입니다.)

     

    예로, UI Thread에서 컨트롤의 상태를 변경하고 있는 도중에 사용자가 만든 Thread가 컨트롤의 상태를 업데이트하려고 한다면 문제가 발생되게 됩니다. 어떤 일을 먼저해야 할지 알 수 없기 때문에 스레드간 경합 또는 교착 상태가 발생됩니다. 그래서 컨트롤은 내부 창 핸들을 관리하는 스레드에서 대리자를 통해 실행을 위임할 수 있도록 하는 Invoke 메소드를 제공하고 있습니다.

     

    Control.Invoke 메소드는 인자로 반환값 및 파라메터가 없는 대리자를 받습니다. 크로스 스레드 관련 많은 예제들이 오래전부터 사용되어 오던 MethodInvoke 대리자를 사용하고 있을겁니다. MS에서 쉽게 컨트롤의 Invoke 메소드를 호출할 수 있도록 미리 만들어 둔 대리자입니다. 하지만, .NET 3.5에서 Linq와 함께 추가된 Action, Func 대리자를 이용해도 됩니다. Action과 Func는 다음에 알아볼 내용이긴 하지만... Syntax가 같기 때문에 아래처럼 코드를 변경해도 됩니다.

    Form1.cs

    private void fileWatcher_Event(object sender, FileSystemEventArgs e)
    {
        this.Invoke((Action)delegate
        {
            txtLog.Text += string.Format("[{0}] {1}{2}", 
                e.ChangeType, 
                e.FullPath, 
                Environment.NewLine);
        });
    }
    

     

     

    물론, 직접 델리게이트를 선언하고 메소드를 할당한 다음 Invoke로 호출해줘도 동작합니다. 스레드에서도 이벤트처럼 미리 만들어둔 델리게이트를 주로 이용하는 편이고, 직접 선언하는 일은 상당히 드뭅니다. 상당히 많은 부분에서 Action, Func, Predicate를 파라미터로 요구하고 있고 무분별하게 델리게이트를 많이 선언하는 것은 관리도 안될뿐더러 낭비이기 때문이죠^^;

     

    업무 자동화 RPA 매크로 제작 및 견젹 문의 ]

     

    개발자에게 후원하기

    MGtdv7r.png

     

    추천, 구독, 홍보 꼭~ 부탁드립니다.

    여러분의 후원이 빠른 귀농을 가능하게 해줍니다~ 답답한 도시를 벗어나 귀농하고 싶은 개발자~

    감사합니다~

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.