문법적 내용과 동작 원리

다음 코드는 C#에서 비동기 작업을 별도의 스레드에서 실행하는 데 사용됩니다:

_ = Task.Run(async () => await RUN());

구성 요소 분석

  1. Task.Run
    • Task.Run은 비동기 작업을 스레드 풀에서 실행하기 위해 사용됩니다.
    • 일반적으로 CPU 바운드 작업을 스레드 풀로 오프로드하여 UI 스레드를 차단하지 않게 하기 위해 사용됩니다.
    • 반환값은 Task입니다.
  2. 람다 표현식 (async () => await RUN())
    • Task.Run의 매개변수로 비동기 람다 함수가 전달되었습니다.
    • async로 선언된 람다 함수는 비동기 작업을 실행할 수 있으며, await를 통해 다른 비동기 작업(RUN)의 완료를 기다립니다.
  3. await RUN()
    • RUN 메서드는 비동기 메서드입니다. 해당 작업이 완료될 때까지 기다리지만, 비동기 흐름을 유지하므로 호출한 스레드를 차단하지 않습니다.
    • 이 코드에서는 RUN 메서드가 완료될 때까지 비동기적으로 처리됩니다.
  4. _ =
    • 반환된 Task 객체를 변수에 할당하지 않고 무시합니다.
    • 실행 중인 작업을 추적하거나 결과를 사용할 필요가 없을 때 주로 사용됩니다.
    • _는 읽지 않는 변수를 나타내는 C# 표준 컨벤션입니다.

동작 원리

  1. Task.Run의 호출:
    • Task.Run은 스레드 풀의 대기열에 람다 함수(async () => await RUN())를 작업으로 추가합니다.
    • 이 작업은 백그라운드 스레드에서 실행됩니다.
  2. 람다 함수 실행:
    • 스레드 풀에서 작업이 실행되면서 람다 함수가 호출됩니다.
    • 람다 함수는 비동기적으로 RUN()을 호출하고, RUN()이 완료될 때까지 비동기적으로 기다립니다.
  3. 비동기 흐름 유지:
    • await 키워드는 비동기 작업(RUN)의 완료를 기다리되, 호출 스레드를 차단하지 않습니다.
    • RUN()이 완료되면 await 뒤의 코드를 계속 실행합니다.
  4. 결과 처리 없음:
    • Task.Run의 반환값(Task)을 무시했기 때문에 작업 결과를 추적하거나 예외를 처리하지 않습니다.

주의사항

  1. 예외 처리:
    • 무시된 Task에서 발생한 예외는 호출자가 확인하지 않기 때문에 런타임 경고가 발생하거나 추적되지 않을 수 있습니다.
    • 안정성을 위해 예외 처리를 추가하거나 반환된 Task를 추적하는 것이 좋습니다.
  2. 예시:
    _ = Task.Run(async () => {
    try { await RUN(); }
    catch (Exception ex)
    { // 예외 로깅 또는 처리 Console.WriteLine($"Error: {ex.Message}");
    }
    });

  3. UI 컨트롤 접근:
    • 백그라운드 스레드에서 UI 컨트롤을 직접 조작하면 문제가 발생합니다. Invoke 또는 SynchronizationContext를 통해 UI 스레드에서 실행해야 합니다.
  4. 성능 고려:
    • Task.Run은 스레드 풀의 작업자 스레드를 사용하므로, 남용하면 시스템 리소스에 영향을 줄 수 있습니다. IO 바운드 작업이나 짧은 작업의 경우 다른 비동기 메서드로 해결하는 것이 더 효율적입니다.

한 줄 요약

이 코드는 비동기 작업을 백그라운드 스레드에서 실행하여 UI 스레드를 차단하지 않도록 설계된 패턴입니다. Task.Run과 비동기 람다를 결합하여 비동기 작업을 효율적으로 실행합니다.



필자의 사용예)

protected override bool ProcessCmdKey(ref Message message, Keys keyData)
{
    const int WM_KEYDOWN = 0x0100;

     switch (message.Msg)
     {
          case WM_KEYDOWN:
          if (keyData.ToString() == "Space")
          {
          // 비동기 메서드를 실행하되, 기다리지 않고 처리
          _ = Task.Run(async () => await RUN());

          return true; // 키가 처리되었음을 나타냄
          }
          break;
          default:
          break;
     }
     return base.ProcessCmdKey(ref message, keyData);
}



Main 메서드와 STAThread 속성

C#에서 비동기 메서드를 동기적으로 호출하는 구조를 포함하고 있습니다. 주로 Task.Run과 await 키워드를 사용하여 비동기 메서드를 처리하는 방식입니다. 이 코드를 더 잘 이해하기 위해 각 부분을 세분화해서 설명드리겠습니다.

1. Main 메서드와 STAThread 속성

  • Main 메서드는 C# 애플리케이션의 진입점(entry point) 입니다.
  • STAThread 속성은 Windows Forms 또는 WPF 애플리케이션에서 메인 스레드가 단일 스레드 아파트(Single-Threaded Apartment, STA) 모드로 실행되도록 지정합니다. 이는 주로 COM 상호 운용성(Interoperability)과 관련이 있습니다. Windows Forms 및 WPF에서 UI 스레드는 STA 모드로 실행되어야 합니다.
// FetchComplexDetailsClass:주로 크롤링작업이 이루어지는 프로그램작업일떄에 쓰이는 호출방법
// FetchComplexDetailsClass:주로 크롤링작업이 이루어지는 프로그램작업일떄에 쓰이는 호출방법
 
[STAThread]
static void Main()
{
     // 비동기 메서드를 호출하는 부분
     Task.Run(async () =>
     {
          FetchComplexDetailsClass frm = new FetchComplexDetailsClass();
          await frm.RUN();
    }).GetAwaiter().GetResult(); // 비동기 작업이 완료될 때까지 대기
}
 

2. Task.Run(async () => { ... })

  • Task.Run은 주어진 델리게이트 또는 메서드를 비동기적으로 실행하기 위해 새로운 태스크를 생성합니다.
  • async () => { ... }는 비동기 메서드를 정의하는 람다 표현식입니다. 이 람다 안에서 비동기 작업을 수행할 수 있습니다.
  • 이 부분은 백그라운드 스레드에서 비동기 작업을 수행하게 합니다. 이 경우 frm.RUN() 메서드를 비동기적으로 호출하려고 합니다.

3. await frm.RUN()

  • await는 비동기 메서드를 기다리게 하며, 메서드가 완료될 때까지 나머지 코드를 실행하지 않습니다.
  • frm.RUN()은 FetchComplexDetailsClass 객체의 RUN이라는 비동기 메서드를 호출합니다.
  • 이 메서드가 완료될 때까지 현재 작업(스레드)이 대기 상태에 들어갑니다. RUN 메서드 내부에서 비동기 작업(예: 파일 I/O, 네트워크 호출 등)을 처리하고 그 결과를 반환할 때까지 기다리게 됩니다.

4. .GetAwaiter().GetResult()

  • .GetAwaiter().GetResult()는 비동기 작업이 완료될 때까지 기다리면서, 비동기 결과를 동기적으로 처리하는 방법입니다.
  • GetAwaiter()는 Task에서 비동기 작업의 상태를 가져옵니다.
  • GetResult()는 비동기 작업이 완료될 때까지 기다리고, 그 결과를 동기적으로 반환합니다. 이 부분에서 예외가 발생하면 AggregateException을 발생시킬 수 있습니다.

5. 전체 흐름 설명

  1. Main 메서드 실행: 프로그램이 시작되면 Main 메서드가 호출됩니다. 여기서는 STAThread 속성 덕분에 단일 스레드 아파트에서 실행됩니다.
  2. Task.Run 실행: Task.Run은 새로운 비동기 작업을 시작합니다. 이 작업은 백그라운드 스레드에서 실행됩니다. async 람다 표현식 안에서 비동기 작업을 호출하고 있습니다.
  3. await frm.RUN(): frm.RUN() 비동기 메서드를 호출하면서, 이 메서드가 완료될 때까지 기다립니다. 이 메서드 내부에서 실제 비동기 작업이 수행됩니다(예를 들어, 네트워크 요청 또는 데이터 처리).
  4. GetAwaiter().GetResult(): await가 내부적으로 비동기 작업을 기다리지만, .GetAwaiter().GetResult()를 통해 해당 작업이 완료될 때까지 현재 스레드를 동기적으로 차단합니다. 즉, 이 코드가 없으면 비동기 메서드의 작업이 완료되기 전에 Main이 종료되었을 수 있기 때문에, 프로그램이 예기치 않게 종료되지 않도록 합니다.
  5. 비동기 메서드 완료 후 종료: frm.RUN() 메서드가 완료되면, Task.Run 내의 비동기 작업이 종료되고, Main 메서드가 종료됩니다. 이제 프로그램이 정상적으로 종료됩니다.

동기와 비동기 결합

  • 비동기 작업은 일반적으로 UI 스레드를 차단하지 않으며, 다른 작업을 처리하는 동안 동시에 실행됩니다.
  • 하지만 GetAwaiter().GetResult()는 비동기 작업을 동기적으로 기다리게 하므로, Main 스레드는 비동기 작업이 완료될 때까지 대기하고, 작업이 완료된 후에 종료됩니다.

6. 왜 이런 방식으로 사용하나요?

  • 비동기 작업을 UI 스레드에서 처리할 때 동기적으로 메인 스레드가 대기하도록 강제하는 경우가 있습니다. 이를 통해 UI를 잠시 멈추게 하지 않고 비동기 작업을 안전하게 처리할 수 있습니다.
  • Task.Run을 사용하면 비동기 작업이 백그라운드에서 실행되므로 UI 스레드의 블로킹을 피할 수 있습니다. 다만, .GetAwaiter().GetResult()를 사용하여 그 결과를 동기적으로 기다리는 방식은 메인 스레드가 작업 완료를 기다리게 만들기 때문에 주로 Main 메서드에서 비동기 작업을 호출할 때 사용됩니다.

결론

  • Task.Run을 통해 비동기 작업을 백그라운드에서 시작하고, await를 사용해 비동기 메서드를 기다립니다.
  • GetAwaiter().GetResult()를 사용하여 비동기 작업이 완료될 때까지 메인 스레드를 동기적으로 대기하게 만듭니다.
  • 이 방식은 주로 콘솔 애플리케이션에서 비동기 작업을 동기적으로 처리하고 싶을 때 사용됩니다.


WindowsBase.dll/PresentationFramework.DLL/PresentationCore.DLL
WindowsBase.dll : WPF를 위한 기본 서비스 제공.
PresentationCore.dll
PresentationFramework.dll : using System.Windows.Controls

1. WPF개요

Windows Presentation Foundation
MS의 UI, 미디어 및 문서가 통합된 프로그래밍 모델(닷넷 3.0에 소개)
브라우저와 데스크톱에서 동시에 실행될 수 있는 프로그래밍 모델 제공.
Markup(XAML)과 코드 프로그래밍을 동시에 지원한다.
디자인이 가능한 프로그램 모델을 개발하였다. (Blend 프로그램으로 가능)
WPF는 DirectX를 Application UI에 통합했다.

2. WPF Architecture

 PresentationFramework
 PresentationCore
 Common Language Runtime

milcore 
 User32 DirectX 
 Kernel


PresentationFramework, PresentationCore : 순수한 관리코드로만 구성. 서로 분리되어 있음.
PresentationCore : API형태의 라이브러리, 컴포넌트 형태


WPF관련 어셈블리
WindowsBase.dll : WPF를 위한 기본 서비스 제공.
  DispatcherObject 
    Dispatcher : 복수의 작업을 대기시킬 수 있는 큐.
    namespace : System.Windows.Threading
    ※ STA : 실행 context에 하나의 쓰레드만 존재.
    Frame Queueing 가능 : Invoke(), BeginInvoke()

  DependencyObject
    WPF의 속성 시스템의 지원을 받기 위해 필요.
    프레임 수준의 바인딩 종속성, 손쉬운 데이터 공유 등을 객체 인스턴스에 저장하지 않고 가능.
    데이터 구조 중심 프로그래밍(XAML에서 이벤트와 액션까지 모델화하고 선언적으로 처리)

PresentationCore.dll
  Visual
    랜더링을 담당하는 객체. 컨트롤 클래스의 시작점. micore 컴포넌트와 밀접한 관련.
    WPF는 멀리 있는 객체를 먼저 그리고 가까이 있는 객체를 나중에 그린다.
    주요 기능 : 출력 표시. 변환, 클리핑, 적중 테스트, 경계 상자 계산.
    입출력, 이벤트 레이아웃 X

  UIElement
    Layout, 입력 및 이벤트 등의 핵심 기능을 담당.
    Measure : 크기를 정의, Arrange : 위치와 정렬. (2단계 레이아웃)
    입력과 Command의 분리 가능. -> CommandBinding으로 관리 가능.

PresentationFramework.dll
  FrameworkElement
    WPF 프레임워크 수준 요소 클래스와 UIElement 서비스를 연결해 주는 지점.
    레이아웃 시스템 정의 : 핵심 기능 손상 없이 새로운 레이아웃을 정의 가능.
    논리적 트리 : markup을 이용해 구조를 표현할 때 이용.
    객체 수명 이벤트 : Initialized, Loaded, Unloaded 세가지 지원.
    데이터 바인딩 및 동적 리소스 참조 지원 : Expression으로 바인딩 -> Framework는 해석.
    스타일 : 컨트롤의 외관을 변형하기 위해 사용.
    에니메이션 지원 

  Control
    탬플릿 지원 기능 : UI를 편리하게 수정 가능. 속성, 이벤트, Command, Template 기능 지원.
     XAML을 통한 탬플릿 정의 -> 랜더링 시 스트립트를 읽어 들여 반영

+ Recent posts