C# Task.GetAwaiter and GetResult with Application.OnExit(Window.Closing)
이철우
C# WPF 앱이 종료할 때 어떤 System.Threading.Tasks.Task로 마무리 하려고 한다면, async/await 구문 대신에 Task.GetAwaiter()와 GetResult()를 사용해야 합니다. 종료 이벤트로 적절한 것은 Application.OnExit(또는 Window.Closing) 입니다.
Visual Studio 2022 Community를 이용하여 .Net 9 WPF 앱 프로젝트를 만듭니다.
App.xaml 파일에서 StartupUri=“MainWindow.xaml” 를 지웁니다.
올려주신 방식대로라면 Dispatcher가 OnExit 내부에서 대기하고 있으므로 D2 파트를 실행할 수 없게 되며, 결과적으로 데드락 상태에 빠지게 됩니다.
WPF에서 비동기 종료 로직을 수행하는 경우에는 ShutdownMode를 OnExplicitShutdown 또는 OnMainWindowClose로 설정한 뒤,
아래와 같이 메인 윈도우가 닫힐 때 모든 비동기 작업을 완료한 후 애플리케이션이 종료되도록 구성하는 방식이 가장 안전하다고 생각합니다.
OnMainWindowClose 일 경우
public partial class MainWindow : Window
{
private bool _isClosing;
protected override async void OnClosing(CancelEventArgs e)
{
if (_isClosing)
return;
e.Cancel = true; // await 수행전 Cancel 설정
_isClosing = true;
try
{
await CleanupAsync();
}
catch (Exception ex) { } // 오류 처리
finally
{
Close(); // 다시 호출
}
}
private async Task CleanupAsync()
{
// 비동기 종료 작업 호출
await SomeTask;
}
}
al6uiz 님이 올려주신대로 SynchronizationContext Main Thread Dispatcher 에 대한 컨텍스트 캡쳐가 일어날 경우에는 문제가 생길 수 있어, 댓글을 남깁니다. await SomethingAsync().ConfigureAwait(false) 및 SomethingAsync().GetAwaiter().GetResult() 양쪽다 OnExit 와 같이 Main Thread 로 블로킹을 할 경우, 문제의 소지가 있습니다.
대표적으로 아래와 같이 Main Thread에서 DbContext 에 대해 수행했을때 터지는데요.
public void OnButtonClick()
{
var users = _dbContext.Users.ToListAsync().GetAwaiter().GetResult();
}
Main Thread 에서 블로킹하며, Task 에 대해 해결할 수 있는 방법은 제가 아는 한, 아래와 같이 Task 로 한번더 감싸는 방법을 사용하거나, 동기식으로 처리해야합니다.
근데 이렇게 처리하는 방법은 async await 를 사용하는 이점을 버리게 됩니다. async awiat 를 최대한 활용하기 위해, al6uiz 님이 설명하시는 대로 Closing 에서 일단 종료 절차를 cancel 후, 정리를 완료하고, 종료 절차를 수행하는 방법이 대안으로 보입니다.