Set up custom provider example

This topic provides an example of how to set up a custom async provider. When writing a custom async provider, async methods are started by a call to XAsyncBegin. This function has a callback parameter that implements, for example, the handling of state changes, enqueueing callbacks, and triggering completion.

struct Context
{
    uint64_t result;
};

Context* context = new Context{ 0 };
// Assume that the async block is already filled out with the completion callback and task queue.
HRESULT hr = XAsyncBegin(async, context, ProviderFuncIdentity, __FUNCTION__,
    [](XAsyncOp op, const XAsyncProviderData* providerData)
    {
        Context* context =
        reinterpret_cast<Context*>(providerData->context);
        switch (op)
        {
            case XAsyncOp::Begin:
                return XAsyncSchedule(providerData->async, 0);

            case XAsyncOp::Cleanup:
                delete context;
                break;

            case XAsyncOp::GetResult:
                memcpy(providerData->buffer, &context->result, sizeof(uint64_t));
                break;

            case XAsyncOp::DoWork:
                context->result = 12345678;
                XAsyncComplete(providerData->async, S_OK, sizeof(uint64_t));
                break;

            case XAsyncOp::Cancel:
                // This call can't be canceled.
                break;
        }

    return S_OK;
});

There's a lot to unpack here, but the basic idea is straightforward: call XAsyncBegin, and give it a provider callback to handle the different state changes. Recall that the async block contains the task queue to use and an optional implementation of a completion callback. The callback must then handle the five different cases for the async task's runtime as shown in the following table.

Provider case Responsibility
Begin Called immediately by the call to XAsyncBegin. This case is responsible for scheduling the async work callback onto the task queue. This is handled by a call to XAsyncSchedule. It enqueues this callback to run again with the DoWork case via the task queue's work port.
Cleanup Called when the async task is finishing, and any internal data that's created for tracking can be deleted. For the previous example code, this case cleans up the Context structure, which was allocated for keeping track of result data.
GetResult Called when the user calls XAsyncGetResult. The case must then write to the output buffer that's passed to the case. The original async result, therefore, must have been saved somewhere. The previous example code saves it in the Context structure.

Asynchronous Microsoft Game Development Kit (GDK) API functions that return data from an async task internally call XAsyncGetResult and convert that data to the form that's suitable for the caller. We recommend this for working with results.
DoWork Called via the task queue that has its work port dispatched. This means that the stack frame of this callback is running on the intended thread based on how the task queue was set up. Any work should be performed here.

In the XAsyncRun internal async provider implementation, this is where it executes the passed-in work callback. For custom providers, work can be done directly in the callback or with some custom method to encapsulate the functionality.

When the work is completed, XAsyncComplete must be called. This function performs two tasks. First, it specifies if any output data is needed by supplying a data size. If a greater-than-zero value is supplied, an internal buffer is allocated for the GetResult case to write to. Second, it enqueues a call to the completion callback to be run on the task queue's completion port. The completion status code passed to XAsyncComplete becomes the code that's returned from a call to XAsyncGetStatus.
Cancel Triggered by an outside call to XAsyncCancel. If this case is unimplemented, as a result, nothing happens. To allow for canceling, the provider must be able to signal to any work being executed to stop. The DoWork case must then call XAsyncComplete with a status of E_ABORT.

See also

XAsyncProvider library overview

Set up invocation methods (example)

Set up return data (example)

Set up Cancelability (example)

XAsyncProvider