스레딩 기능 마이그레이션

이 항목에서는 UWP(유니버설 Windows 플랫폼) 애플리케이션의 스레딩 코드를 Windows 앱 SDK로 마이그레이션하는 방법을 설명합니다.

API 및/또는 기능 차이점 요약

UWP의 스레딩 모델은 재진입을 차단하고 다양한 재진입 버그 및 교착 상태를 방지하는 데 도움이 되는 ASTA(Application STA)라는 STA(단일 스레드 아파트) 모델의 변형입니다. ASTA 스레드는 UI 스레드라고도 합니다.

Windows 앱 SDK 동일한 재진입 보호 장치를 제공하지 않는 표준 STA 스레딩 모델을 사용합니다.

CoreDispatcher 유형은 DispatcherQueue로 마이그레이션됩니다. 그리고 CoreDispatcher.RunAsync 메서드는 DispatcherQueue.TryEnqueue로 마이그레이션됩니다.

C++/WinRT. CoreDispatcher와 함께 winrt::resume_foreground를 사용하는 경우 대신 DispatcherQueue를 사용하도록 마이그레이션합니다.

ASTA-STA 스레딩 모델

ASTA 스레딩 모델에 대한 자세한 내용은 애플리케이션 STA에 대한 특별한 점은? 블로그 게시물을 참조하세요.

Windows 앱 SDK STA 스레딩 모델은 재진입 문제를 방지하는 것과 동일한 보증을 갖지 않으므로 UWP 앱이 ASTA 스레딩 모델의 재입력되지 않는 동작을 가정하는 경우 코드가 예상대로 작동하지 않을 수 있습니다.

주의해야 할 한 가지는 XAML 컨트롤로의 재입력입니다(UWP 사진 편집기 샘플 앱(C++/WinRT)의 Windows 앱 SDK 마이그레이션에 있는 예 참조). 그리고 액세스 위반과 같은 일부 충돌의 경우 직접 충돌 호출 스택이 일반적으로 사용하기에 적합한 스택입니다. 그러나 예외 코드가 0xc000027b인 허용된 예외 충돌인 경우 올바른 호출 스택을 가져오려면 더 많은 작업이 필요합니다.

허용된 예외

허용된 예외 충돌은 가능한 오류를 저장하고 코드의 어떤 부분도 예외를 처리하지 않으면 나중에 사용됩니다. XAML은 경우에 따라 오류가 치명적이라고 즉시 결정하는데, 이 경우 직접 충돌 스택이 좋을 수 있습니다. 그러나 스택이 치명적이라고 판단되기 전에 해제되는 경우가 더 많습니다. 허용된 예외에 대한 자세한 내용은 Inside Show 에피소드 허용된 예외 C000027B를 참조하세요.

중첩된 메시지 펌프를 보거나 XAML 컨트롤의 특정 예외가 throw되는 것을 보기 위해 Windows 디버거(WinDbg)(Windows용 디버깅 도구 다운로드 참조)에서 크래시 덤프를 로드한 다음 !pde.dse를 통해 저장된 예외를 덤프하여 허용된 예외 충돌에 대한 자세한 정보를 얻을 수 있습니다.

PDE 디버거 확장(!pde.dse 명령용)은 OneDrive에서 PDE*.zip 파일을 다운로드하여 사용할 수 있습니다. 해당 zip 파일의 적절한 x64 또는 x86 .dll을 WinDbg 설치의 winext 디렉터리에 넣으면 !pde.dse가 허용된 예외 크래시 덤프에서 작동합니다.

끝에 처리되거나 무시된 일부가 포함된 여러 저장 예외가 있는 경우가 많습니다. 가장 일반적으로 첫 번째 허용된 예외는 흥미로운 예외입니다. 어떤 경우에는 첫 번째 허용된 예외가 두 번째 예외를 다시 throw하는 것일 수 있으므로 두 번째 허용된 예외가 첫 번째와 동일한 스택에 더 깊게 표시되면 두 번째 예외가 오류의 원인일 수 있습니다. 각 허용된 예외와 함께 표시되는 오류 코드도 해당 예외와 연결된 HRESULT를 제공하기 때문에 중요합니다.

Windows.UI.Core.CoreDispatcher를 Microsoft.UI.Dispatching.DispatcherQueue로 변경

이 섹션은 UWP 앱에서 Windows.UI.Core.CoreDispatcher 클래스를 사용하는 경우에 적용됩니다. 여기에는 DependencyObject.DispatcherCoreWindow.Dispatcher 속성과 같이 CoreDispatcher를 사용하거나 반환하는 모든 메서드 또는 속성의 사용이 포함됩니다. 예를 들어 Windows.UI.Xaml.Controls.Page에 속한 CoreDispatcher를 검색할 때 DependencyObject.Dispatcher를 호출합니다.

// MainPage.xaml.cs in a UWP app
if (this.Dispatcher.HasThreadAccess)
{
    ...
}
// MainPage.xaml.cpp in a UWP app
if (this->Dispatcher().HasThreadAccess())
{
    ...
}

대신 Windows 앱 SDK 앱에서 Microsoft.UI.Dispatching.DispatcherQueue 클래스를 사용해야 합니다. 그리고 DependencyObject.DispatcherQueueMicrosoft.UI.Xaml.Window.DispatcherQueue 속성과 같이 DispatcherQueue를 사용하거나 반환하는 해당 메서드 또는 속성. 예를 들어 Microsoft.UI.Xaml.Controls.Page(대부분의 XAML 개체는 DependencyObject)에 속한 DispatcherQueue를 검색할 때 DependencyObject.DispatcherQueue를 호출합니다.

// MainPage.xaml.cs in a Windows App SDK app
if (this.DispatcherQueue.HasThreadAccess)
{
    ...
}
// MainPage.xaml.cpp in a Windows App SDK app
#include <winrt/Microsoft.UI.Dispatching.h>
...
if (this->DispatcherQueue().HasThreadAccess())
{
    ...
}

DispatcherQueue.TryEnqueue로 CoreDispatcher.RunAsync 변경

이 섹션은 Windows.UI.Core.CoreDispatcher.RunAsync 메서드를 사용하여 기본 UI 스레드(또는 특정 Windows.UI.Core.CoreDispatcher와 연결된 스레드)에서 실행할 작업을 예약하는 경우에 적용됩니다.

// MainPage.xaml.cs in a UWP app
public void NotifyUser(string strMessage)
{
    if (this.Dispatcher.HasThreadAccess)
    {
        StatusBlock.Text = strMessage;
    }
    else
    {
        var task = this.Dispatcher.RunAsync(
            Windows.UI.Core.CoreDispatcherPriority.Normal,
            () => StatusBlock.Text = strMessage);
    }
}
// MainPage.cpp in a UWP app
void MainPage::NotifyUser(std::wstring strMessage)
{
    if (this->Dispatcher().HasThreadAccess())
    {
        StatusBlock().Text(strMessage);
    }
    else
    {
        auto task = this->Dispatcher().RunAsync(
            Windows::UI::Core::CoreDispatcherPriority::Normal,
            [strMessage, this]()
            {
                StatusBlock().Text(strMessage);
            });
    }
}

Windows 앱 SDK 앱에서 대신 Microsoft.UI.Dispatching.DispatcherQueue.TryEnqueue) 메서드를 사용합니다. DispatcherQueue와 연결된 스레드에서 실행될 작업을 Microsoft.UI.Dispatching.DispatcherQueue에 추가합니다.

// MainPage.xaml.cs in a Windows App SDK app
public void NotifyUser(string strMessage)
{
    if (this.DispatcherQueue.HasThreadAccess)
    {
        StatusBlock.Text = strMessage;
    }
    else
    {
        bool isQueued = this.DispatcherQueue.TryEnqueue(
        Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal,
        () => StatusBlock.Text = strMessage);
    }
}
// MainPage.xaml.cpp in a Windows App SDK app
#include <winrt/Microsoft.UI.Dispatching.h>
...
void MainPage::NotifyUser(std::wstring strMessage)
{
    if (this->DispatcherQueue().HasThreadAccess())
    {
        StatusBlock().Text(strMessage);
    }
    else
    {
        bool isQueued = this->DispatcherQueue().TryEnqueue(
            Microsoft::UI::Dispatching::DispatcherQueuePriority::Normal,
            [strMessage, this]()
            {
                StatusBlock().Text(strMessage);
            });
    }
}

winrt::resume_foreground 마이그레이션(C++/WinRT)

이 섹션은 C++/WinRT UWP 앱의 코루틴에서 winrt::resume_foreground 함수를 사용하는 경우에 적용됩니다.

UWP에서 winrt::resume_foreground의 사용 사례는 실행을 포그라운드 스레드로 전환하는 것입니다(해당 포그라운드 스레드는 Windows.UI.Core.CoreDispatcher와 연결된 스레드인 경우가 많음). 여기 그 예가 있습니다.

// MainPage.cpp in a UWP app
winrt::fire_and_forget MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
{
    ...
    co_await winrt::resume_foreground(this->Dispatcher());
    ...
}

Windows 앱 SDK 앱에서:

따라서 먼저 Microsoft.Windows.ImplementationLibrary NuGet 패키지에 대한 참조를 추가합니다.

그런 다음 대상 프로젝트의 pch.h에 다음 포함을 추가합니다.

#include <wil/cppwinrt_helpers.h>

그런 다음 아래 표시된 패턴을 따릅니다.

// MainPage.xaml.cpp in a Windows App SDK app
...
winrt::fire_and_forget MainPage::ClickHandler(IInspectable const&, RoutedEventArgs const&)
{
    ...
    co_await wil::resume_foreground(this->DispatcherQueue());
    ...
}