푸터 콘텐츠로 바로가기
Iron Academy Logo
C# 배우기
C# 배우기

다른 카테고리

C# 비동기 및 대기 마스터하기

Tim Corey
38분 57초

비동기 프로그래밍은 복잡한 용어 때문에 어렵게 느껴질 수 있지만, 애플리케이션의 속도와 반응성을 크게 향상시키는 데 상당한 이점을 제공합니다. Tim Corey의 " C# Async / await - 비동기 프로그래밍으로 앱의 반응성과 속도를 높이는 방법 " 영상은 이러한 기능을 효과적으로 사용하는 방법에 대한 실용적인 가이드를 제공합니다.

이 글에서는 팀 코리의 상세한 영상을 활용하여 이러한 개념들을 분석하고, 명확한 예시를 통해 async와 await의 실제 적용 사례를 보여드리겠습니다. 비동기 이벤트 핸들러부터 여러 작업 처리, 동기화 컨텍스트, 오류 처리, 그리고 async void 메서드 사용과 같은 일반적인 함정에 이르기까지 다양한 시나리오를 다룰 것입니다. 이 글을 끝까지 읽으시면 비동기 프로그래밍에 대한 확실한 이해와 C# 애플리케이션에 비동기 프로그래밍을 구현하는 방법을 알게 될 것입니다.

소개

C#에서 비동기 프로그래밍은 특히 I/O 집약적 작업이나 CPU 집약적 작업을 처리할 때 애플리케이션 성능을 향상시키는 데 필수적입니다. asyncawait 키워드는 개발자가 비동기 코드를 작성하여 메인 스레드를 차단하지 않고 장기 실행 작업을 실행할 수 있게 해줍니다. 메서드를 async 키워드로 표시함으로써 메서드가 비동기 작업을 수행하여 Task 또는 Task<t>를 반환할 것임을 알립니다. await 키워드는 비동기 메서드 내에서 사용되어 기다리고 있는 작업이 완료될 때까지 실행을 일시 중지하며, 다른 작업들을 동시에 실행할 수 있도록 합니다.

동기 프로그래밍에서는 작업이 순차적으로 실행되어 호출 스레드를 차단할 수 있는 반면, 비동기 방식을 사용하면 백그라운드 프로세스나 사용자 입력 처리와 같은 여러 작업을 UI 스레드를 멈추지 않고 동시에 처리할 수 있습니다. 이 기사에서는 asyncawait가 서로 어떻게 작동하는지를 탐구하고, 비동기 void 메서드, 오류 처리, 컨텍스트 전환, 동기화 컨텍스트와 같은 개념을 다룰 것입니다. 이 과정에서는 여러 비동기 작업을 효율적으로 관리하고 예외를 처리하는 방법을 배우게 되며, 이를 통해 호출 스레드, UI 스레드 및 작업 완료 전반에 걸쳐 원활한 실행을 보장할 수 있습니다. 이 접근 방식은 응답성이 중요한 파일 시스템, 네트워크 요청 또는 동시 요청과 관련된 시나리오에서 특히 유용합니다.

팀은 동기식 작업과 비동기식 작업의 차이점을 설명하면서 비동기 프로그래밍을 소개합니다. 동기 프로그래밍에서는 작업이 순차적으로 수행되므로 특정 작업에 시간이 오래 걸리면 지연이 발생합니다. 비동기 프로그래밍은 작업을 병렬로 또는 백그라운드에서 실행할 수 있도록 하여 성능과 응답성을 향상시킵니다.

비동기 프로그래밍의 장점

Tim은 비동기 프로그래밍의 두 가지 주요 이점을 강조합니다.

  1. 사용자 인터페이스(UI) 응답성 향상 : 작업을 비동기적으로 수행함으로써, 장시간 소요되는 작업 중에도 UI의 응답성이 유지됩니다.

  2. 병렬 실행 : 독립적인 작업들을 병렬로 실행하여 전체 완료 시간을 단축할 수 있습니다.

데모 애플리케이션 사용법 안내

팀은 동기식 작업과 비동기식 작업의 차이점을 보여주기 위해 Windows Presentation Foundation(WPF) 데모 애플리케이션을 설치합니다. 이 애플리케이션에는 두 개의 버튼이 있습니다. 하나는 작업을 동기적으로 실행하는 버튼이고, 다른 하나는 작업을 비동기적으로 실행하는 버튼입니다.

데모 애플리케이션

이 애플리케이션에는 출력 결과를 표시하는 결과 창도 포함되어 있습니다.

동기식 작동

팀은 동기 작동의 기반이 되는 코드를 설명합니다. Stopwatch는 작업의 실행 시간을 측정하는 데 사용됩니다.

private void ExecuteSync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    List<string> websites = GetWebsiteList();
    foreach (var website in websites)
    {
        string result = DownloadWebsite(website);
        ReportWebsiteInfo(website, result);
    }
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

private List<string> GetWebsiteList()
{
    return new List<string>
    {
        "https://www.yahoo.com",
        "https://www.google.com",
        "https://www.microsoft.com",
        "https://www.cnn.com",
        "https://www.codeproject.com",
        "https://www.stackoverflow.com"
    };
}

private string DownloadWebsite(string websiteURL)
{
    WebClient client = new WebClient();
    return client.DownloadString(websiteURL);
}

private void ReportWebsiteInfo(string website, string data)
{
    ResultsWindow.Text += $"{website}: {data.Length} characters{Environment.NewLine}";
}
private void ExecuteSync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    List<string> websites = GetWebsiteList();
    foreach (var website in websites)
    {
        string result = DownloadWebsite(website);
        ReportWebsiteInfo(website, result);
    }
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

private List<string> GetWebsiteList()
{
    return new List<string>
    {
        "https://www.yahoo.com",
        "https://www.google.com",
        "https://www.microsoft.com",
        "https://www.cnn.com",
        "https://www.codeproject.com",
        "https://www.stackoverflow.com"
    };
}

private string DownloadWebsite(string websiteURL)
{
    WebClient client = new WebClient();
    return client.DownloadString(websiteURL);
}

private void ReportWebsiteInfo(string website, string data)
{
    ResultsWindow.Text += $"{website}: {data.Length} characters{Environment.NewLine}";
}

이 코드는 다음 단계를 수행합니다.

  1. 스톱워치 시작 : 실행 시간을 측정합니다.
  2. 웹사이트 목록 가져오기 : 웹사이트 URL 목록을 가져옵니다.
  3. 각 웹사이트 다운로드 : 각 웹사이트의 콘텐츠를 다운로드합니다.
  4. 웹사이트 정보 보고 : 다운로드된 콘텐츠의 URL과 길이를 표시합니다.
  5. 스톱워치 정지 : 타이머를 정지하고 총 실행 시간을 표시합니다.

동기 작동 관찰

팀은 동기식 작동을 시연하기 위해 애플리케이션을 실행합니다. 그는 웹사이트 다운로드가 진행되는 동안 사용자 인터페이스가 응답하지 않게 되며, 다운로드가 완료된 후에 결과가 한꺼번에 표시된다고 지적합니다.

동기 작업

형태가 움직이지 않는다는 것을 실제로 확인하려면 영상 9분 20초 부분을 시청하여 더 잘 이해하시기 바랍니다.

비동기 작업 생성

팀은 동기식 방식을 비동기식 방식으로 변환함으로써 첫 번째 문제를 해결합니다. 이는 asyncawait 키워드를 사용하는 것을 포함합니다. 다음은 그 과정을 단계별로 설명한 것입니다.

  1. 기존 동기 메서드 복사 :

    • Tim은 기존의 동기식 메서드를 복사하고 비동기식 메서드임을 나타내도록 이름을 변경합니다.
    private async Task RunDownloadAsync()
    {
       // Same code as RunDownloadSync, but will be modified for async
    }
    private async Task RunDownloadAsync()
    {
       // Same code as RunDownloadSync, but will be modified for async
    }
  2. 비동기 실행을 위한 다운로드 호출 수정 :

    • 팀은 Task.Run에 다운로드 호출을 래핑하여 비동기적으로 실행합니다.
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       return await Task.Run(() => DownloadWebsite(websiteURL));
    }
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       return await Task.Run(() => DownloadWebsite(websiteURL));
    }
    • await 키워드는 메서드가 비동기 작업이 완료될 때까지 기다리도록 보장합니다.
  3. 메서드가 비동기식인지 확인하십시오 .

    • 메서드 시그니처는 async 키워드를 포함하도록 업데이트되며, Task를 반환합니다.
    private async Task RunDownloadAsync()
    {
       List<string> websites = GetWebsiteList();
       foreach (var website in websites)
       {
           var result = await DownloadWebsiteAsync(website);
           ReportWebsiteInfo(website, result);
       }
    }
    private async Task RunDownloadAsync()
    {
       List<string> websites = GetWebsiteList();
       foreach (var website in websites)
       {
           var result = await DownloadWebsiteAsync(website);
           ReportWebsiteInfo(website, result);
       }
    }
  4. 이벤트를 올바르게 처리하십시오 .

    • 버튼 클릭 이벤트 핸들러가 새로운 비동기 메서드를 호출하도록 업데이트되었습니다.
    private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
    {
       Stopwatch stopwatch = Stopwatch.StartNew();
       await RunDownloadAsync();
       stopwatch.Stop();
       ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
    }
    private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
    {
       Stopwatch stopwatch = Stopwatch.StartNew();
       await RunDownloadAsync();
       stopwatch.Stop();
       ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
    }
    • 이벤트 핸들러는 비동기 메서드를 호출하더라도 void를 반환할 수 있다는 점에 유의하십시오.

UI 반응성 문제 해결

팀은 await 키워드를 사용함으로써 UI가 반응성을 유지한다는 것을 보여줍니다. 이는 비동기 작업이 실행되는 동안 사용자가 창과 상호작용할 수 있게 해줍니다.

// UI remains responsive
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
// UI remains responsive
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

정확한 타이밍 확보

전체 실행 시간이 올바르게 보고되도록 보장하기 위해, 팀은 버튼 클릭 이벤트 핸들러의 호출에 await 키워드를 추가합니다.

// Correctly waits for the asynchronous task to complete
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
// Correctly waits for the asynchronous task to complete
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

비동기 메서드가 완료될 때까지 기다림으로써 실행 시간을 정확하게 측정할 수 있으며, 결과는 즉시 표시됩니다.

병렬 비동기 생성

팀은 각 작업이 순차적으로 완료될 때까지 기다리는 방식의 한계를 병렬 실행을 통해 해결합니다. 그가 이를 위해 코드를 수정하는 방법은 다음과 같습니다.

  1. 기존 비동기 메서드를 복사합니다 .

    • Tim은 기존의 비동기 메서드를 복제하고 병렬 실행을 나타내도록 이름을 변경합니다.
    private async Task RunDownloadParallelAsync()
    {
       // Parallel execution logic will be added here
    }
    private async Task RunDownloadParallelAsync()
    {
       // Parallel execution logic will be added here
    }
  2. 작업 목록 작성 :

    • 모든 다운로드 작업을 저장하기 위한 작업 목록이 생성됩니다.
    List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
    List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
  3. 대기 없이 모든 작업을 시작하세요 .

    • Tim은 각 다운로드 작업을 즉시 기다리는 대신 작업 목록에 추가합니다.
    foreach (var website in websites)
    {
       tasks.Add(DownloadWebsiteAsync(website));
    }
    foreach (var website in websites)
    {
       tasks.Add(DownloadWebsiteAsync(website));
    }
  4. 모든 작업이 완료될 때까지 기다리세요 .

    • Task.WhenAll 메서드는 모든 작업의 완료를 기다리는 데 사용됩니다. 이 메서드는 모든 작업이 완료되면 결과 배열을 반환합니다.
    WebsiteDataModel[] results = await Task.WhenAll(tasks);
    WebsiteDataModel[] results = await Task.WhenAll(tasks);
  5. 결과 처리 :

    • 모든 작업이 완료되면 Tim은 반복문을 통해 결과를 처리합니다.
    foreach (var result in results)
    {
       ReportWebsiteInfo(result);
    }
    foreach (var result in results)
    {
       ReportWebsiteInfo(result);
    }

다음은 병렬 실행 메서드의 전체 코드입니다.

private async Task RunDownloadParallelAsync()
{
    List<string> websites = GetWebsiteList();
    List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();

    foreach (var website in websites)
    {
        tasks.Add(DownloadWebsiteAsync(website));
    }

    WebsiteDataModel[] results = await Task.WhenAll(tasks);

    foreach (var result in results)
    {
        ReportWebsiteInfo(result);
    }
}
private async Task RunDownloadParallelAsync()
{
    List<string> websites = GetWebsiteList();
    List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();

    foreach (var website in websites)
    {
        tasks.Add(DownloadWebsiteAsync(website));
    }

    WebsiteDataModel[] results = await Task.WhenAll(tasks);

    foreach (var result in results)
    {
        ReportWebsiteInfo(result);
    }
}

이벤트 핸들러 업데이트

Tim은 버튼 클릭 이벤트 핸들러를 업데이트하여 새로운 병렬 실행 방식을 호출하도록 합니다.

private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadParallelAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}
private async void ExecuteAsync_Click(object sender, RoutedEventArgs e)
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    await RunDownloadParallelAsync();
    stopwatch.Stop();
    ResultsWindow.Text += $"Total execution time: {stopwatch.ElapsedMilliseconds} ms{Environment.NewLine}";
}

RunDownloadParallelAsync을 기다림으로써 전체 실행 시간이 정확하게 측정되고 보고됩니다.

성능 향상 관찰

팀은 병렬 실행을 통해 애플리케이션을 실행하여 성능 향상을 입증합니다. 결과는 순차 실행에 비해 전체 실행 시간이 크게 단축되었음을 보여줍니다.

// Parallel execution
async Task RunDownloadParallelAsync();
// Parallel execution
async Task RunDownloadParallelAsync();

웹사이트들이 동시에 다운로드되면서 전체 대기 시간이 가장 느린 개별 다운로드 시간으로 단축되므로 속도 향상이 확연히 드러납니다.

Task.Run() 내에서 메서드를 래핑하는 것 vs 비동기 메서드 호출

팀은 원본 메서드를 수정할 수 없는 경우 동기 메서드를 래핑하여 비동기화하는 Task.Run()의 개념을 설명합니다. 하지만 그는 또한, 만약 해당 메서드를 제어할 수 있다면 메서드 자체를 비동기식으로 수정하는 것이 더 바람직한 접근 방식임을 보여줍니다.

  1. Task.Run() 내에서 메서드를 래핑하기:

    • 이 방법은 원래 메서드의 코드를 변경할 수 없지만 비동기적으로 실행하고 싶을 때 유용합니다.
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       return await Task.Run(() => DownloadWebsite(websiteURL));
    }
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       return await Task.Run(() => DownloadWebsite(websiteURL));
    }
  2. 메서드를 비동기식으로 만들기 :

    • 메서드를 수정할 수 있다면, 적절한 비동기 API를 사용하여 메서드 자체를 비동기식으로 변환하는 것이 좋습니다.

    • 팀은 DownloadWebsiteDownloadWebsiteAsync로 변경하고 WebClientDownloadStringTaskAsync을 사용하는 것으로 이를 데모합니다.
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       WebClient client = new WebClient();
       string data = await client.DownloadStringTaskAsync(websiteURL);
    
       return new WebsiteDataModel { URL = websiteURL, Data = data };
    }
    private async Task<WebsiteDataModel> DownloadWebsiteAsync(string websiteURL)
    {
       WebClient client = new WebClient();
       string data = await client.DownloadStringTaskAsync(websiteURL);
    
       return new WebsiteDataModel { URL = websiteURL, Data = data };
    }
  3. 호출 방식 조정 :

    • 메서드를 변환한 후, 호출 코드는 Task.Run() 래퍼를 제거하고 직접 비동기 메서드를 호출하도록 조정되어야 합니다.
    private async Task RunDownloadParallelAsync()
    {
       List<string> websites = GetWebsiteList();
       List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
    
       foreach (var website in websites)
       {
           tasks.Add(DownloadWebsiteAsync(website));
       }
    
       WebsiteDataModel[] results = await Task.WhenAll(tasks);
    
       foreach (var result in results)
       {
           ReportWebsiteInfo(result);
       }
    }
    private async Task RunDownloadParallelAsync()
    {
       List<string> websites = GetWebsiteList();
       List<Task<WebsiteDataModel>> tasks = new List<Task<WebsiteDataModel>>();
    
       foreach (var website in websites)
       {
           tasks.Add(DownloadWebsiteAsync(website));
       }
    
       WebsiteDataModel[] results = await Task.WhenAll(tasks);
    
       foreach (var result in results)
       {
           ReportWebsiteInfo(result);
       }
    }

핵심 요약

Tim은 async와 await 사용에 대한 몇 가지 중요한 핵심 사항을 정리하며 마무리합니다.

  • Task 반환 메서드 보장: 메서드를 async으로 표시할 때는 이벤트 핸들러를 제외하고 void 대신 Task 또는 Task<t>를 반환해야 합니다.

  • 신뢰할 수 있는 작업을 위한 await 사용: 비동기 작업의 결과가 필요할 때는 await 키워드를 사용하십시오.

  • 수정할 수 없는 코드에 대한 Task 래핑: 원본 메서드를 수정할 수 없는 경우 동기 메서드를 래핑하기 위해 Task.Run()을 사용하세요.

  • 비동기 메서드를 적절히 표시하기: 항상 메서드 이름에 Async를 추가하여 비동기 작업임을 나타내십시오.

  • 병렬 실행 vs 순차 실행 : 작업 간의 의존성을 기반으로 작업을 병렬로 실행할지, 아니면 서로 대기해야 할지 결정합니다.

팀은 C#에서의 비동기 프로그래밍이 async, awaitTask로 인해 더 간단해졌다고 강조합니다. 컨텍스트 스레딩 및 아파트 모델과 같은 복잡한 요소는 백그라운드에서 관리됩니다.

결론

Tim Corey의 C# async 및 await 튜토리얼은 비동기 프로그래밍을 마스터하는 데 매우 귀중한 자료를 제공합니다. Tim은 작업 병렬 처리, UI 응답성, 그리고 async와 await를 적절한 상황에서 사용하는 것의 중요성과 같은 개념들을 세심하게 설명함으로써 개발자들이 더 빠르고 반응성이 뛰어난 애플리케이션을 만들 수 있도록 필요한 도구를 제공합니다. 그의 상세한 설명은 메인 스레드를 차단하지 않고 장시간 실행되는 작업을 효율적으로 처리하는 방법과 비동기 작업 및 병렬 실행을 관리하는 전략을 보여줍니다.

더 깊은 이해와 추가적인 실제 사례를 원하시면 팀 코리의 영상을 시청하시기를 강력히 추천합니다. 비동기 void 메서드, 오류 처리 및 동기화 컨텍스트 관리에 대한 그의 통찰력은 프로젝트에서 이러한 개념을 구현하는 능력을 더욱 향상시켜 줄 것입니다.

Hero Worlddot related to C# 비동기 및 대기 마스터하기
Hero Affiliate related to C# 비동기 및 대기 마스터하기

사랑하는 것을 공유하여 더 많은 수익을 얻으세요

당신은 .NET, C#, Java, Python, 또는 Node.js를 다루는 개발자를 위한 콘텐츠를 만드나요? 당신의 전문성을 추가 수입으로 전환하세요!

아이언 서포트 팀

저희는 주 5일, 24시간 온라인으로 운영합니다.
채팅
이메일
전화해