C# Task.GetAwaiter and GetResult with Application.OnExit(Window.Closing)

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โ€ ๋ฅผ ์ง€์›๋‹ˆ๋‹ค.

// App.xaml

<Application x:Class="Study.Wpf.TestGetAwaiter.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Study.Wpf.TestGetAwaiter">
    <Application.Resources>
    </Application.Resources>
</Application>

App.xaml.cs ์—์„œ ํด๋ž˜์Šค App์„ ์•„๋ž˜์ฒ˜๋Ÿผ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ Task.Delay๊ฐ€ ๋งˆ๋ฌด๋ฆฌ ์ž‘์—…์„ ๋Œ€์‹ ํ•ฉ๋‹ˆ๋‹ค.

// App.xaml.cs

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        AllocConsole();
        Console.WriteLine(nameof(OnStartup));

        MainWindow = new MainWindow();
        MainWindow.Show();
    }

    protected override void OnExit(ExitEventArgs e)
    {
        Task.Delay(3000).GetAwaiter().GetResult();
        Console.WriteLine(nameof(Task.Delay));
        Console.ReadLine();

        Console.WriteLine(nameof(OnExit));
        FreeConsole();
        base.OnExit(e);
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool AllocConsole();

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool FreeConsole();
}

์ด๊ฒƒ์„ ๋นŒ๋“œํ•˜๊ณ  ์‹คํ–‰ํ•˜๊ณ  ์ข…๋ฃŒํ•˜๋ฉด, 3์ดˆ ๋’ค์— 'Delayโ€™๊ฐ€ ์ถœ๋ ฅ๋จ(๋งˆ๋ฌด๋ฆฌ)์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Task.Delay(3000).GetAwaiter().GetResult(); ๋Œ€์‹ ์— ์•„๋ž˜์ฒ˜๋Ÿผ
await Task.Delay(3000).ConfigureAwait(false); ๋ฅผ ํ™œ์šฉํ•˜๋ฉด 'Delayโ€™๊ฐ€ ์ถœ๋ ฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

protected override async void OnExit(ExitEventArgs e)
{
    //Task.Delay(3000).GetAwaiter().GetResult();
    await Task.Delay(3000).ConfigureAwait(false);
    Console.WriteLine(nameof(Task.Delay));
    Console.ReadLine();

    Console.WriteLine(nameof(OnExit));
    FreeConsole();
    base.OnExit(e);
}

๋น„๋™๋น„ ํ”„๋กœ๊ทธ๋žจ์—์„œ ๋˜๋„๋ก ์‚ฌ์šฉํ•˜์ง€ ๋ง๋ผ๋˜ Task.GetAwaiter()์™€ GetResult()๊ฐ€ ์œ„ ์ƒํ™ฉ์—์„œ ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

4๊ฐœ์˜ ์ข‹์•„์š”

await ํ•ด๋ฒ„๋ฆฌ๋ฉด ์ง„์ž… ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•œ ์ธก์—์„œ ๋ฐ”๋กœ ๋ฆฌํ„ด๋˜์–ด ๋ฒ„๋ฆฌ๊ณ 
์ดํ›„ ๊ณง์ด์–ด ์•ฑ ์ข…๋ฃŒ ์ ˆ์ฐจ๊ฐ€ ์ง„ํ–‰๋˜๋ฉด์„œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์—์„œ await ํ•œ ๊ฑฐ๋ณด๋‹ค ๋จผ์ € ์•ฑ์ด ์ข…๋ฃŒ๋˜๋Š” ์ƒํ™ฉ ๊ฐ™์•„ ๋ณด์ด๋„ค์š”.

์˜คํžˆ๋ ค blocking ์ด ํ•„์š”ํ•œ ์ƒํ™ฉ์ด๋ผ ์ด๋ ‡๊ฒŒ ํ•˜๋Š” ๊ฒŒ ์˜๋ฏธ ์žˆ๊ฒ ๋„ค์š”.

์ƒ๊ฐ ๋ชปํ–ˆ๋˜ ์ƒํ™ฉ์ธ๋ฐ ์š”๊ฑด ์œ ์šฉํ•  ๊ฑฐ ๊ฐ™์•„์š”.

๋‹ค๋งŒ ์ด๋Ÿฐ ๋ถ€์ž‘์šฉ(?) ๋“ฑ์„ ๊ณ ๋ คํ•˜๋ฉด

์ข…๋ฃŒ์ ˆ์ฐจ๋Š” ๋น„๋™๊ธฐ๋ฅผ ์•ˆ ํ•˜๋Š”๊ฒŒ ์ •์‹ ๊ฑด๊ฐ•์ด ์ด๋กญ์Šต๋‹ˆ๋‹ค.. =ใ…=!

5๊ฐœ์˜ ์ข‹์•„์š”

์ ์–ด๋„ SynchronizationContext๊ฐ€ Dispatcher์ธ WPF ํ™˜๊ฒฝ์—์„œ๋Š” ์˜ฌ๋ ค์ฃผ์‹  ๋‚ด์šฉ์ด ๋ฐ”๋žŒ์งํ•œ ๋ฐฉํ–ฅ์€ ์•„๋‹Œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

OnExit ํ•จ์ˆ˜ ๋‚ด์—์„œ ๋Œ€๊ธฐ ์ค‘์ธ Task ์•ˆ์—์„œ ๋˜ ๋‹ค๋ฅธ Task๋ฅผ awaitํ•˜๋Š” ๊ฒฝ์šฐ ๋ฐ๋“œ๋ฝ์„ ์œ ๋ฐœํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.

    private async Task ReleaseAsync()
    {
        Console.WriteLine("D1");
        await Task.Delay(10);
        Console.WriteLine("D2"); 
    }

์œ„์˜ ์ฝ”๋“œ๋Š” ConfigureAwait(false) ์—†์ด await๊ฐ€ ์ปจํ…์ŠคํŠธ๋ฅผ ์บก์ฒ˜ํ•œ ์ƒํƒœ๋กœ ์ง„์ž…ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— Dispatcher๊ฐ€ ๋‹ค์Œ ํŒŒํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์„ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

    protected override void OnExit(ExitEventArgs e)
    {
        ReleaseAsync().GetAwaiter().GetResult();
        ...

์˜ฌ๋ ค์ฃผ์‹  ๋ฐฉ์‹๋Œ€๋กœ๋ผ๋ฉด 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;
    }
}

OnExplicitShutdown ์ผ ๊ฒฝ์šฐ

        ...
        MainWindow.Closed += Handler_MainWindowClosed;
    }
    
    private async void Handler_MainWindowClosed(object? sender, EventArgs e)
    {
        try
        {
            await CleanupAsync();
        }
        catch (Exception ex) { } // ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ
        finally
        {
            Application.Current.Shutdown();
        }
    }

    private async Task CleanupAsync()
    {
        // ๋น„๋™๊ธฐ ์ข…๋ฃŒ ์ž‘์—… ํ˜ธ์ถœ
        await SomeTask;
    }
}
4๊ฐœ์˜ ์ข‹์•„์š”

์ฃผ์˜ํ•  ์ ์€

  1. ํ”„๋กœ์ ํŠธ ์†์„ฑ์˜ Output type : Windows Application
  2. ShutdownMode๋Š” OnExplicitShutdown๊ฐ€ ์•„๋‹ ๋•Œ.
    [AllocConsole();๊ณผ FreeConsole(); ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ถœ๋ ฅ]
  3. ๊ด€์‹ฌ์‚ฌ ๋ถ„๋ฆฌ : ํด๋ž˜์Šค Application์—์„œ ์‹œ์ž‘, ์ข…๋ฃŒ ์ž‘์—…์„ ์ˆ˜ํ–‰.

์ž์—ฐ์Šค๋Ÿฐ ๋งˆ๋ฌด๋ฆฌ ์ž‘์—…(Task)์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  Window.Closing ์ด๋ฒคํŠธ๋ฅผ ์“ฐ๋ ค๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด ํ•ฉ๋‹ˆ๋‹ค.

    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            AllocConsole();

            Console.WriteLine(nameof(OnStartup));

            MainWindow = new MainWindow();
            MainWindow.Closing += MainWindow_Closing;
            MainWindow.Show();
        }

        private void MainWindow_Closing(object? sender, System.ComponentModel.CancelEventArgs e)
        {
            MainWindow.Closing -= MainWindow_Closing;

            Task.Delay(3000).GetAwaiter().GetResult();
            Console.WriteLine(nameof(Task.Delay));
        }

        protected override void OnExit(ExitEventArgs e)
        {
            //Task.Delay(3000).GetAwaiter().GetResult();
            //Console.WriteLine(nameof(Task.Delay));
            Console.ReadLine();

            Console.WriteLine(nameof(OnExit));
            FreeConsole();
            base.OnExit(e);
        }

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool AllocConsole();

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool FreeConsole();
    }