Right now do !

[WinForm] UI Freeze 해결 해주는 비동기 호출

by 지금당장해

 10년 전이나 지금이나 기업용 솔루션 개발을 하는 일이 어찌 보면 비슷한 패턴의 연속이다. 다만 나를 비롯한 개발자들은 이를 좀더 간결한 코드로 만들어 내느냐에 노력 해온거 같다. 3-tier 구조 역시 그러하다. 물론 오늘 다룰 주제가 3-tier 구조에 국한되는 이야기는 아니다. 3 tier던 2 tier던 UI에서 서비스 호출 또는 DB Access를 하고 나서 UI가 할일은 대기 하는 것이다. 결과가 올때 까지 무작정 (사실은 정해진 시간 내에서 기다린다. 우린 이것을 보통 Timeout이라고 한다. ) 기다린다. 

 

이렇게 호출을 해놓고 기다리면 화면이 얼어 버린다. 얼어 버린 화면을 이리 끌고 저리 끌고 해봐야 화면은 아무런 응답을 하지 못한다. 왜 이러냐면 UI를 가지고 있는 윈도우 프로그램은 윈도우를 갱신하는 Thread를 가지고 있는데 우리가 보통 이 Thread에서 서비스를 호출한다. 이 호출 하고나면 UI Thread는 UI를 갱신하는 일은 내팽개친 채 서비스 혹은 DB의 호출 결과를 기다린다고 망부석 상태에 돌입한다. 그럼 사용자의 응답을 무시 하는 것 뿐만 아니라 혹여라도 다른 윈도우에 의해 가려 지기라도 했다가 벗어나면 검댕이가 되기도 한다.




아 프로그램이 뭐 돌기만 하면 되지라고 생각 하시는 분은 여기서 브라우저를 닫기 바란다. 이 상태를 그대로 두면 일단 작성하다 만 프로그램 같다. 그리고 혹여나 동작을 중지(Abort)라도 하려 해도 방법이 없다. 왜냐 하면 이 망부석은 님이 돌아 오시기 전까지는 아무 일도 않하기 때문에 혹여 내가 Abort버튼이라도 달아서 동작을 시키려 해도 답이 없다.


 이런 문제를 해결 하려면 하던 일을 버리고 망무석이 되는 UI Thread대신 서비스 호출 혹은 DB Access를 대신 해줄 Thread를 하나 두어 호출 하면 된다. 근데 대부분 업무 프로그램의 특성상 Thread한테 이런 long term 작업을 맞겨두고 UI가 마냥 사용자의 요청을 받을수도 없다. 

왜냐 하면 사용자가 결과를 받을 화면을 닫을 수도 갱신 시켜 버릴 수도 있기 때문이다. 개발자가 원하는 답이 아니다. 


난 그저 화면만 안 얼었으면 혹여라도 있을지 모를 사용자의 중단 요청 정도를 받을 수 있으면 한다. 이와 관련 하여 필자가 예전부터 자주 사용 하던 패턴이 있는데 이름하여 원격 호출 대리자 Form이다. 일반 적인 구성은 아래 그림과 같다.




이 패턴은 그간에도 많이 사용 했던 패턴인데 근래에 적용한 코드는 C#의 무명함수 및 람다식 등이 적용된 좀더 세련된 코드다. 위 그림에서 2. Modal Dialog Show하기 전에 생략된 내용이 있는데 처리의 주입이다.  즉 이 단계에서 아래 코드와 같이 오래 걸리는 처리를 정의하여 익명 함수로 정의하고 Wait Form의 Fuction이라고 정의된 Action 대리자에 주입 한다. (이를 5단계에서 호출 한다.)


private void button2_Click(object sender, EventArgs e)
{
     object result = null;
     AsyncCallForm form = new AsyncCallForm();

     RpcUtility rpc = new RpcUtility();
                       
     // 오래 걸리는 작업을 주입 함

     form.Function = (() 
     => 
     {
         result = rpc.CallRemoteMethod();                
     });
                     

     form.ShowDialog();

     MessageBox.Show(result.ToString());
}


[UI Form의 호출 부]


약간 설명의 순서가 뒤집힌거 같긴 하지만.. 호출 부분터 언급을 했다. 다음은 원격 호출을 대리 해줄 Dialog의 정의를 보자. 위 호출 부에서 Function으로 정의된 대리자가 정의 되어 있고 이를 Shown 이벤트에서 새로운 Thread를 생성하여 호출하는 구조로 되어 있다. 


이 코드는 검색을 하다. 참고를 하였는데 원작 URL을 보관 하지 못하여 출처를 밝히지 못하는점 유감스럽다. 원작자가 본다면 좀 불쾌 하더라도 양해 바란다.




using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace jchong.exam
{
    public partial class AsyncCallForm : Form
    {
        public AsyncCallForm()
        {
            InitializeComponent();
            this.Shown += AsyncCallForm_Shown;
        }

        public Action Function { get; set; }

        private void AsyncCallForm_Shown(object sender, EventArgs e)
        {
            var thread = new Thread(
            () =>
            {
                Function?.Invoke();
                this.Invoke(
                    (Action)(() =>
                    {
                        this.Close();
                    }));
            });

            thread.Start();
        }
    }
}


Function에 주입된 처리를 한 후에 this.Close()를 하는데 직접 호출하지 않고 이를 다시 Action으로 Casting하여 this.Invoke(...) 한다. 이는 UI Thread가 아닌 Thread에서는 UI객체를 직접 호출 할 수 없기 때문이다.(예외가 발생한다.) 관련 자료는 인터넷에 많으니까... 필자는 오늘 여기 까지....

블로그의 정보

지금 당장 해!!!

지금당장해

활동하기