ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Async/Await] Invoke보다 간편하게 UI 접근하기
    .NET/개념 및 유용한 팁 2019. 12. 19. 23:46
    반응형
    비동기 프로그래밍(async, await)에 대한 올바른 사용법에 대한 문서가 있습니다.
    https://ddochea.tistory.com/212 를 참조해주세요.

     

    2015년도에 작성했던 포스트 "2015/01/11 - [C#] - [Invoke & BeginInvoke] 1. 다른 Thread 에서 UI 접근하기 (1)" 가 현재까지도 부동의 1순위를 기록하고 있다. 일간 방문자수의 절반이상이 해당 포스트 관련 접근이다. 멱살잡고 캐리중

    그런데 좀 찝찝하다. 2012년쯤? 부터 .NET 4.5 및 C# 5.0가 발표되면서 async, await가 소개되었었고, 2015년에 Invoke & BeginInvoke 다루는 나도 참 늦게배운다 생각했는데, 2020년이 오는 중에도 Invoke와 BeginInvoke가 꾸준히 검색유입량1위다.

    Invoke와 BeginInvoke가 나쁘다는 건 아니다. 여전히 필요로 하며, 특히 .NET 4.0, C# 5.0 이상을 사용할 수 없는 Visual Studio 환경에선 필수적이다. 그래도 소스코드는 간결하면 간결할 수록 유지보수하기 좋다는게 내 생각인지라, 앞으로 해당 글을 통해 유입되는 개발자분들에게 자연스럽게 안내해 드리고자 이번 포스트를 작성한다.

    아래 소스코드는 "2015/01/11 - [C#] - [Invoke & BeginInvoke] 1. 다른 Thread 에서 UI 접근하기 (1)" 포스트의 예제 소스를 async, await와 Task 방식으로 변경한 소스이다.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace AsyncAwaitExample
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
    
            }
    
            private async void btnCalc_Click(object sender, EventArgs e)
            {
                long cnt = 0;
                long result = 0;
                if (long.TryParse(txt_X.Text, out cnt))
                {
                    var calcTask = Task.Run(() => // 시대가 시대인지라 람다식으로 썼다. () => 대신 delegate() 로 바꿔도 무방하다.
                    {
                        result = Calc(cnt);
                    });
                    await calcTask; // calcTask 가 완료될때까지 대기. 완료되면 바로 다음줄인 lbl_Result.Text = result.ToString();가 실행된다.
                    lbl_Result.Text = result.ToString();
                }
                else
                {
                    lbl_Result.Text = "지정한 숫자가 잘못되었습니다.";
                }
            }
    
            private long Calc(long cnt)
            {
                long result = 0;
                for (long i = 0; i < cnt; i++)
                {
                    result += 2;
                }
                return result;
            }
        }
    }

    변경된 사항은 btnCalc_Click 함수 뿐이다. (using 네임스페이스 달라진건 애교로 봐달라.)
    Click 이벤트 함수에 async 와 calcTask에 await가 붙은 것에 주목해야한다.

    UI 이벤트 함수에 async가 붙으면 이 함수는 UI 스레드에 비동기적으로 동작해야하는 작업이 있음을 의미한다. 해당 예제에선 calcTask가 비동기작업이다. Task.Run() 함수는 람다식 내부의 작업을 실행한다는 뜻을 의미한다. 지난 예제의 Thread.Start() 와 역할이 비슷하나, async 없이 Task.Run()만 호출하면 작업을 UI Thread에서 실행하게되는 차이가 있다.

    await 는 대기를 의미한다. Run함수로 생성한 calcTask 작업이 모두 끝날때까지 await calcTask 소스코드의 다음줄에 있는 작업을 진행하지 못하도록 로직을 대기시킨다. 따라서 await 전에 결과값을 대입하려는 행위를 하면 원하는 결과를 화면에 표시할 수 없으니 주의해야한다.

    lbl_Result.Text = result.ToString(); // 이러면 Calc 완료전 값인 0 값만 나오게된다.
    await calcTask; // calcTask 가 완료될때까지 대기. 완료되면 바로 다음줄인 lbl_Result.Text = result.ToString();가 실행된다.
    

    로직을 대기시킨다고 해서 UI 가 멈추는 것은 아니므로 별도의 취소기능을 구현하여 작업을 조기종료시키도록 유도할 수도 있다.

    Invoke 때 설명했던 것처럼, UI Thread(또는 Main Thread라고도 한다.)가 아닌 곳에서 직접 UI 컨트롤을 수정하는 행위는 허용하지 않으므로, Task 안에 직접 UI를 편집하는 코드를 넣어선 안된다.

     

    끝!

    AsyncAwaitExample.zip
    0.03MB

    반응형

    댓글

Designed by Tistory.