본문 바로가기

IT

C# 비동기 async / await + Task 이해와 예제

C# 에서 비동기로 개발이 필요한 순간

 

▶ 상황 요약

  • UI에는 카운트를 계속 보여주는 TextBox가 있고
    → 이건 실시간으로 계속 업데이트되어야 합니다.
  • 사용자가 "저장" 버튼을 누르면 무거운 저장 작업을 해야 함
    → 이 작업은 오래 걸릴 수 있지만,
    그 사이에도 UI(카운트 텍스트박스)는 계속 살아 있어야 함

 

🔸 동기(synchronous)로 저장하면?

 

private void btnSave_Click(object sender, EventArgs e)
{
    SaveData(); // 저장 중에 UI 멈춤
}

 

✅ 문제: SaveData()가 무거운 작업이라면
❌ 그 동안 TextBox 업데이트가 멈추고, UI가 응답하지 않게 됩니다.
(버튼 클릭도 안 되고, 텍스트박스도 안 바뀜) 곤란곤란...

 

 

🔸 비동기(async)로 저장하면?

 

private async void btnSave_Click(object sender, EventArgs e)
{
    await SaveDataAsync(); // 저장 중에도 UI는 멀쩡히 동작
}

 

이렇게 하면 UI 쓰레드는 그대로 살아있고,
SaveDataAsync()는 백그라운드에서 돌아가고,
TextBox는 계속 카운트가 올라갈 수 있답니다! (강력한 기능^^)

 

 

🔹 예시 코드 (카운트 + 저장 버튼)

 

int count = 0;
bool isRunning = true;

private void Form1_Load(object sender, EventArgs e)
{
    Task.Run(async () =>
    {
        while (isRunning)
        {
            count++;
            Invoke(new Action(() => txtCount.Text = count.ToString()));
            await Task.Delay(1000); // 1초마다 증가
        }
    });
}

private async void btnSave_Click(object sender, EventArgs e)
{
    btnSave.Enabled = false;
    await SaveDataAsync(); // 비동기로 저장
    btnSave.Enabled = true;
}

private async Task SaveDataAsync()
{
    // 무거운 작업 시뮬레이션 (예: 파일 저장, DB 처리)
    await Task.Delay(5000); // 5초 걸리는 저장
    // 실제 저장 코드 여기에
}

 


  이 구조 덕분에 텍스트박스는 실시간 카운트업 유지하고
무거운 저장은 자연스럽게 비동기 처리됩니다.

 

이런 구조가 바로 "UI는 유연하게 유지하면서,

백그라운드에서 무거운 작업을 하는" 모범 케이스입니다.


 

🔹 Invoke()가 필요한 이유

WinForms나 WPF처럼 UI 요소는 "UI 스레드"에서만 접근 가능합니다.

즉, 다른 스레드(예: Task.Run, Thread, Timer)에서

UI 컨트롤(txtCount 같은 텍스트박스 등) 을 직접 수정하면 오류가 납니다.

 

 

txtCount.Text = "100"; // ← 이 코드가 UI 스레드가 아닌 곳에서 실행되면 예외 발생!

 

 

🔸 그래서 Invoke()가 필요하답니다.

Invoke()는 UI 스레드에게 "이 코드 좀 대신 실행해줘!" 하고 부탁하는 거죠~

 

 

this.Invoke(new Action(() => {
    txtCount.Text = count.ToString();
}));

 

이렇게 하면, 현재 코드가 UI 스레드가 아니라도
안전하게 txtCount.Text를 바꿀 수 있습니다.

 

 

🔹 정리해 보면

UI 스레드 화면을 그리는 주 스레드 (폼, 버튼, 텍스트박스 등을 관리)
작업 스레드 Task, Thread 등 백그라운드에서 일하는 스레드
Invoke() UI 스레드에게 "이 코드는 네가 실행해줘!"라고 요청하는 방법

 

 

🔸 예시 (왜 Invoke()가 필요한지)

 

Task.Run(() =>
{
    // 백그라운드에서 동작 중
    txtCount.Text = "100"; // ❌ 예외 발생 (UI 스레드 아님)

    this.Invoke(new Action(() =>
    {
        txtCount.Text = "100"; // ✅ UI 스레드가 대신 실행
    }));
});

 

 

🔸 Invoke() vs BeginInvoke() 차이

Invoke() UI 스레드가 끝날 때까지 기다림 (동기)
BeginInvoke() 바로 다음 코드로 넘어감 (비동기)

 

 

 

C#의 비동기 프로그래밍은 초보자에게 약간 헷갈릴 수 있지만,
한 번 이해하고 나면 정말 강력한 도구가 될 수 있습니다.^^