App threading and DirectX

Applies to Windows and Windows Phone

The app object that defines the run-time representation of your Windows Store app uses a threading model called Application Single-Threaded Apartment (ASTA) to host your app’s UI views. In this topic, we discuss how the threading model for UI views works and how to develop your Windows Store app using DirectX with C++ to use this threading model effectively.

The app object and ASTA

If you've worked with COM, you're probably familiar with the single-threaded apartment (STA) model for thread behavior. ASTA, introduced with Windows 8, also uses a single thread in an apartment, and any calls to objects instanced in that apartment will run on that thread. When your app initially activates, the CoreApplication object for your app creates an ASTA to host the view for the activation. Your app’s UI object will run in this apartment, and work not related to UI should be delegated to other threads. The Parallel Patterns Library (PPL) async pattern is used to perform work requested by the UI thread so that the work can be offloaded to background threads while keeping the UI responsive. (Be aware that the call object is typically marked as agile, and that no marshaling across apartments is necessary.)

So what makes an ASTA different from an STA? Here are a few differences:

  • You cannot create an ASTA yourself. ASTAs are created by the app object to host UI views. An ASTA is only used for UI, and you cannot create UI threads yourself—you must rely on the app object to create them for you.
  • You cannot create STAs from the app singleton yourself. However, you can create Multi-threaded Apartments (MTAs) using the Windows Runtime Windows::System::Threading APIs.
  • The ASTA does not allow reentrancy into the ASTA. This rule improves your efficiency and safety in writing UI code as it guarantees that the UI thread will not receive calls for work unrelated to the work the UI is currently performing. In order to accept arbitrary calls, the UI must call CoreDispatcher::ProcessEvents, which checks for event messages and invokes any callbacks you've created for those events.

An ASTA is designed to handle the UI for Windows Store apps. This list highlights some features of an ASTA:

  • There may be multiple UI threads per process.
  • It executes all calls on a single thread.
  • Reentrancy is disallowed.
  • It's used specifically for UI operations.

Contrast this with an MTA, where there is one apartment per process, where there are one or more threads per apartment, and where calls are dispatched concurrently on any thread.

ASTA and thread reentrancy

When you develop a Windows Store app using DirectX, you can create new UI threads from the CoreWindow for your app, but you also expose your app to thread reentrancy issues. Here we talk about how this threading model works and how to develop your Windows Store app using DirectX in a way that prevents UI thread deadlocks.

In COM, your thread performs "reentrancy" when it interrupts the execution of one method's code to begin running another method's code. For example, reentrancy can occur when your thread is processing a change to a context menu, and, while waiting for the call to complete, you make a call to, say, process a specific plug-in for that context menu before the change has been fully processed. The call creates attempts to re-enter your main app thread despite the fact that the result of the prior call, which the call has a dependency on, isn't complete . As a result of this reentrancy, your main app thread stalls or becomes corrupted, and the app crashes, or data associated with the app also becomes corrupted. This is a problem because most Windows Store apps are written using async patterns, where the code for other calls can be processed while your app thread waits for a prior call to finish processing and return.

The ASTA addresses this issue by expressly blocking thread reentrancy. In order to allow the UI to remain responsive, async calls should be used to move work off the UI thread. Because reentrancy is disallowed, cases of reentrancy can be surfaced as deadlocks in your application. . This also allows you to quickly detect the reentrancy behavior in your code and fix your logic during debugging. (In the example above, you would address the issue by making sure that the context menu change call had completed before processing a plug-in for the menu.)

Specifically, here are a few things to avoid doing from the main app thread to eliminate reentrancy deadlock:

  • Do not code design your app to require calls from COM or Windows Runtime methods while you are waiting for another call to complete. This would require reentrancy which is explicitly disallowed.
  • Do not make outgoing calls that can potentially take a long time to complete. This will block your app's UI thread, making it unresponsive. Use async calls instead.
  • If code must be executed on the UI thread (for example, if you need to call an object on the UI thread), use the CoreDispatcher to dispatch the call to the UI thread.

Here are some general solutions to common reentrancy deadlock problems that you might encounter in your Windows Store app:

  • Use the Windows Runtime Windows::System::Threading APIs, like ThreadPool, to create new threads. Threads created using the APIs in this namespace are always initialized as MTA.
  • Keep your outbound calls short to minimize the risk of another outbound call deadlocking with the previous call before the previous call returns.
  • Be careful when using wait primitives like CoWaitForMultipleObjects. Waiting with one of the primitives can cause your UI to block and thus prevent calls from returning to the ASTA. Consider removing these wait primitives from your code.
  • Don't wait for a Windows Runtime or COM call to complete. The call will not complete while you are waiting. The call will only complete when you call CoreDispatcher::ProcessEvents from inside your main app loop (defined in the IFrameworkView::Run method of your view provider implementation).
  • Get your main thread back to calling CoreDispatcher::ProcessEvents as soon as possible.
  • Always use the CoreDispatcher to execute code on the ASTA.

ASTA and the CoreDispatcher object

As mentioned previously, the CoreWindow for your app has an instance of the event message dispatcher, CoreDispatcher. To create threads using it, you must call CoreDispatcher::ProcessEvents on the CoreWindow instance, like so:

CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

Make the call to CoreDispatcher::ProcessEvents on each iteration of the loop you defined in the IFrameworkView::Run method of your view provider implementation, like this:


void MyDirectXApp::Run()
{
    while ()
    {
        CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
        // update the render target(s) and present the swap chain
    }
}

Every time CoreDispatcher::ProcessEvents is called, the callbacks for the specific event message types in the queue are invoked, and the work done in these callbacks are handled on a separate threads.

ASTA considerations for DirectX devs

If you are developing a Windows Store app using DirectX, you're familiar with the properties of an ASTA, because any thread you dispatch from your Windows Store app using DirectX must use the Windows::System::Threading APIs, or use CoreWindow::CoreDispatcher. (You can get the CoreWindow object for the ASTA by calling CoreWindow::GetForCurrentThread from your app.)

The most important thing for a you to be aware of, as a developer of a Windows Store app using DirectX, is that you must enable your app thread to dispatch MTA threads by setting Platform::MTAThread on main().


[Platform::MTAThread]
int main(Platform::Array<Platform::String^>^)
{
    auto myDXAppSource = ref new MyDXAppSource(); // your view provider factory 
    CoreApplication::Run(myDXAppSource);
    return 0;
}

When the app object for your Windows Store app using DirectX activates, it creates the ASTA that will be used for the UI view. The new ASTA thread calls into your view provider factory, to create the view provider for your app object, and as a result your view provider code will run on that ASTA thread.

Also, any thread that you spin off from the ASTA must be in an MTA. Be aware that any MTA threads that you spin off can still create reentrancy issues and result in a deadlock.

If you're porting existing code to run on the ASTA thread, keep these considerations in mind:

  • Wait primitives, such as CoWaitForMultipleObjects, behave differently in an ASTA than in a STA.
  • The COM call modal loop operates differently in an ASTA. You can no longer receive unrelated calls while an outgoing call is in progress. For example, the following behavior will create a deadlock from an ASTA (and immediately crash the app):
    1. The ASTA calls an MTA object and passes an interface pointer P1.
    2. Later, the ASTA calls the same MTA object. The MTA object calls P1 before it returns to the ASTA.
    3. P1 cannot enter the ASTA as it's blocked making an unrelated call. However, the MTA thread is blocked as it tries to make the call to P1.
    You can resolve this by :
    • using the async pattern defined in the Parallel Patterns Library (PPLTasks.h), and
    • calling CoreDispatcher::ProcessEvents from your app's ASTA (the main thread of your app) as soon as possible to allow arbitrary calls.
    That said, you cannot rely on immediate delivery of unrelated calls to your app's ASTA. For more info about async calls, read Asynchronous programming with C++.

Overall, when designing your Windows Store app using DirectX, use the CoreDispatcher for your app's CoreWindow and CoreDispatcher::ProcessEvents to handle all UI threads rather than trying to create and manage your MTA threads yourself. When you need a separate thread that you cannot handle with the CoreDispatcher, use async patterns and follow the guidance mentioned earlier to avoid reentrancy issues.

Related topics

Asynchronous programming with C++
Processes, Threads, and Apartments
Windows Runtime core application and window objects

 

 

Show:
© 2014 Microsoft