[Previous] [Contents] [Next]

Making Asynchronous Calls

In Microsoft Windows 2000, COM+ includes support for asynchronous calls. A client thread can execute a method asynchronously. COM+ starts the call and then immediately returns control back to the client. The client thread is then free to do other work and can later obtain the results of the method call. If the client thread reaches a point where it can no longer do any useful work without the results of the asynchronous method call, it can block until the object has finished processing the call. COM+ also enables the client thread to cancel the method call. For example, the client might decide that it no longer needs the results of the method call or that the object is simply taking too long.

Defining Asynchronous Interfaces

As with all other programming tasks in COM+, Interface Definition Language (IDL) is the starting place for defining asynchronous interfaces. For each interface defined in IDL that you want to call asynchronously, you must add the async_uuid IDL attribute. When the Microsoft IDL (MIDL) compiler sees this attribute in a COM+ interface definition, it automatically generates two versions of that interface: one for traditional synchronous use and the other for asynchronous calls. The asynchronous version of the interface will have the same name as the synchronous interface except with an Async prefix. For example, the asynchronous version of the IUnknown interface defined in the unknown.idl system IDL file is named AsyncIUnknown.

For the asynchronous version of an interface, MIDL splits every method in the interface into two methods named Begin_methodname and Finish_methodname. Each Begin_ method accepts all of the [in] and [in, out] parameters of the synchronous version of that method; each Finish_ method includes all of the [out] and [in, out] parameters. The interface definition shown below includes the async_uuid attribute; notice that the asynchronous version of the interface has its own globally unique identifier (GUID):

[ object, uuid(10000001-AAAA-0000-0000-A00000000001),  // IID_IPrime
    async_uuid(10000001-AAAA-0000-0000-B00000000001) ] // IID_AsyncIPrime
interface IPrime : IUnknown
{
    HRESULT IsPrime(int testnumber, [out, retval] int* retval);
}

The corresponding asynchronous version of this interface generated by MIDL in the component.h file is shown below. Notice that the single IsPrime method of the synchronous IPrime interface has been converted to the Begin_IsPrime and Finish_IsPrime methods of the asynchronous AsyncIPrime interface. The [in] parameter (testnumber) of the IPrime::IsPrime method becomes an argument of the AsyncIPrime::Begin_IsPrime method. The [out] parameter (retval) of the IPrime::IsPrime method becomes an argument of the AsyncIPrime::Finish_IsPrime method.

MIDL_INTERFACE("10000001-AAAA-0000-0000-B00000000001")
AsyncIPrime : public IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE Begin_IsPrime( 
        int testnumber) = 0;
    
    virtual HRESULT STDMETHODCALLTYPE Finish_IsPrime( 
        /* [out][retval] */ int __RPC_FAR *retval) = 0;
};

Interesting entries are created in the HKEY_CLASSES_ROOT\Interface section of the registry when you build and register the proxy/stub code generated by the MIDL compiler for an asynchronous interface. The entry for the interface identifier (IID) of the synchronous version of the interface contains a subkey named AsynchronousInterface that references the IID of the asynchronous version of the interface. The entry for the IID of the asynchronous version of the interface contains a subkey named SynchronousInterface that points back to the synchronous version. Figure 17-1 shows all of the registry entries created by the proxy/stub dynamic-link library (DLL).

Click to view at full size.

Figure 17-1. The registry entries created by a proxy/stub DLL for an asynchronous interface.

Calling Asynchronous Interfaces

Typically, a single coclass does not implement both the synchronous and asynchronous versions of an interface. Instead, the coclass that implements the synchronous version of the interface acts as a class object for the coclass that implements the asynchronous version. A separate object, known as a call object, implements the asynchronous version of the interface. The coclass that implements the synchronous version of the interface also implements the standard ICallFactory interface. The ICallFactory interface has only one method, CreateCall, which is shown below in IDL notation:

interface ICallFactory : IUnknown
{
    HRESULT CreateCall(
        [in]  REFIID                riid,
        [in]  IUnknown             *pCtrlUnk,
        [in]  REFIID                riid2,
        [out, iid_is(riid2)] IUnknown **ppv );
}

The client calls QueryInterface to obtain the ICallFactory interface pointer. Using this pointer, the client calls the ICallFactory::CreateCall method to instantiate the call object that implements the asynchronous version of the desired interface. The client can then call the Begin_ method to start the asynchronous call, followed sometime later with a call to the Finish_ method to obtain the results of the asynchronous call. The following code fragment illustrates these steps:

IPrime* pPrime = 0;
hr = CoCreateInstance(CLSID_InsideCOM, 0, CLSCTX_LOCAL_SERVER, 
    IID_IPrime, (void**)&pPrime);

ICallFactory* pCallFactory = 0;
hr = pPrime->QueryInterface(IID_ICallFactory, 
    (void**)&pCallFactory);

AsyncIPrime* pAsyncPrime = 0;
hr = pCallFactory->CreateCall(IID_AsyncIPrime, 0, 
    IID_AsyncIPrime, (IUnknown**)&pAsyncPrime);

int result = 0;
hr = pAsyncPrime->Begin_IsPrime(testnumber);

// Do other work here.

// Sometime later...
hr = pAsyncPrime->Finish_IsPrime(&result);
if(result)
    cout << endl << testnumber << " is prime." << endl;

pAsyncPrime->Release();
pCallFactory->Release();
pPrime->Release();

Each call object can process only one asynchronous call at a time. If a second asynchronous call is invoked on the same call object while the first call is still executing, the Begin_ method returns RPC_S_CALLPENDING. The client calls the Finish_ method when it is ready to retrieve the results of the asynchronous call. If the asynchronous call is still executing when the Finish_ method is called, the Finish_ method automatically blocks the client thread until the object has completed executing the call. The Finish_ method must be called before a new asynchronous call can be issued on the call object. If you're not interested in the values returned by the Finish_ method and don't intend to issue another asynchronous call on the call object, you can simply release the call object; COM+ detects this condition and automatically cleans up the call.

If you simply want to find out whether the object has finished processing the asynchronous call, you should query the call object for the ISynchronize interface and call the ISynchronize::Wait method. The ISynchronize interface is shown below in IDL notation:

interface ISynchronize : IUnknown
{
    // Waits for the synchronization object to be signaled or 
    // for a specified timeout period to elapse, whichever 
    // comes first
    HRESULT Wait([in] DWORD dwFlags, [in] DWORD dwMilliseconds);

    // Sets the synchronization object's state to signaled
    HRESULT Signal();

    // Resets the synchronization object to the 
    // not signaled state
    HRESULT Reset();
}

The ISynchronize::Wait method accepts two parameters: an options flag from the COWAIT_FLAGS enumeration shown below, and the number of milliseconds to block before timing out. COM+ provides two built-in implementations of the ISynchronize interface: CLSID_StdEvent and CLSID_ManualResetEvent. Because the system provides these two coclasses that implement ISynchronize, there is typically no need to implement this interface; if your object wants to expose ISynchronize, you can simply aggregate one of the two built-in implementations.

typedef enum tagCOWAIT_FLAGS
{
    COWAIT_WAITALL = 1,
    COWAIT_ALERTABLE = 2
} COWAIT_FLAGS;

Internally, the system-provided implementation of the ISynchronize::Wait method calls the CoWaitForMultipleHandles function. This synchronization primitive waits for handles to be signaled or for the specified timeout period to elapse. In the STA model, CoWaitForMultipleHandles enters a modal message loop so it won't freeze the application's user interface; in the MTA model, this function simply puts the calling thread to sleep until the handles become signaled or the timeout period expires. So to find out whether an asynchronous call has completed, you simply call the ISynchronize::Wait method with a timeout of 0, as shown below:

hr = pSynchronize->Wait(0, 0);
if(hr == RPC_S_CALLPENDING)
    cout << "Call is still pending." << endl;

Implementing Asynchronous Interfaces

Objects that want to support asynchronous calls need to implement the ICallFactory interface. The ICallFactory::CreateCall method is called by a client that is preparing to make an asynchronous call. The implementer of this method should instantiate a call object that implements the asynchronous version of the interface. The first parameter of the CreateCall method comes from the client; it specifies the IID of the asynchronous interface for which the client wants a call object. In the code fragment shown below, the object supports only the AsyncIPrime interface; if the client has requested any other interface, an error is returned.

The second parameter of CreateCall is a pointer to the controlling IUnknown. Call objects are typically aggregated by the system so that COM+ can provide automatic implementation of interfaces such as ISynchronize. Even though the client typically passes null for this parameter, COM+ automatically aggregates the object on the server side and provides the call object with a pointer to the controlling unknown of the outer object. The third and fourth parameters of CreateCall are standard QueryInterface-style arguments. The client specifies which interface implemented by the call object it would like in the third parameter, and that interface pointer is returned in the fourth parameter.

A sample implementation of the ICallFactory::CreateCall method is shown below. Note that because the call object is aggregated by COM+, it must provide a delegating version of QueryInterface in addition to the standard implementation. The call object's nondelegating implementation of QueryInterface need only check for the IUnknown and AsyncIPrime interfaces.

HRESULT CInsideCOM::CreateCall(REFIID riid, IUnknown* pCtrlUnk, 
    REFIID riid2, IUnknown** ppv)
{
    HRESTUL hr = 0;

    if(riid != IID_AsyncIPrime)
        return E_INVALIDARG;

    CPrimeCall* pPrimeCall = new CPrimeCall(pCtrlUnk);
    hr = pPrimeCall->Init();
    hr = pPrimeCall->QueryInterface_NoAggregation(riid2, 
        (void**)ppv);
    pPrimeCall->Release_NoAggregation();
    return hr;
}

The CPrimeCall constructor simply stores the IUnknown pointer of the outer object (named pCtrlUnk in the code above) for use in its delegating implementation of IUnknown. The call object is then initialized in the CPrimeCall::Init method, as shown below. Assuming that the call object is being aggregated by COM+, we query the outer object for the ISynchronize interface and store that interface pointer for later use when signaling the client that an asynchronous call is complete. The ISynchronize interface pointer is eventually released in the CPrimeCall destructor.

HRESULT CPrimeCall::Init()
{
    if(m_pCtrlUnk)
        return m_pCtrlUnk->QueryInterface(IID_ISynchronize, 
            (void**)&m_pSynchronize);
    return E_UNEXPECTED;
}

When the client invokes the Begin_IsPrime message to start an asynchronous call, COM+ calls the Begin_IsPrime method in the call object. The Begin_IsPrime method does its work in the usual fashion, but instead of returning the result back to the client, it stores the return value in a member variable (m_retval) of the call object. Then it signals the outer object's synchronization object to let the client know that the asynchronous call is complete. The changed portions of the IsPrime method are shown in boldface below:

HRESULT CPrimeCall::Begin_IsPrime(int testnumber)
{
    unsigned long count;
    unsigned long halfnumber = testnumber / 2 + 1;
    HRESULT hr = 0;

    for(count = 2; count < halfnumber; count++)
        if(testnumber % count == 0)
        {
            m_retval = false;
            m_pSynchronize->Signal();
            return S_OK;
        }

    m_retval = true;
    m_pSynchronize->Signal();
    return S_OK;
}

The implementation of the AsyncIPrime::Finish_IsPrime method simply returns the m_retval value saved in the call object by the Begin_IsPrime method, as shown below. Note that only the Begin_ method of an asynchronous interface is actually called asynchronously; the client thread is always blocked during calls to Finish_ methods. For this reason, it is desirable to do as little processing as a possible in a Finish_ method.

HRESULT CPrimeCall::Finish_IsPrime(int* retval)
{
    *retval = m_retval;
    return S_OK;
}

Interoperability

Although every asynchronous interface has a unique IID, asynchronous and synchronous interfaces are considered two parts of one interface. This idea leads to some interesting scenarios in which a client might attempt to invoke methods of an asynchronous interface on an object that implements only the synchronous version of that interface. In such cases, the client can trap the failed QueryInterface call for the ICallFactory interface and default to the synchronous version of the interface.

This solution, however, places the onus on the client to detect and compensate for a feature not provided by the object. To better support interoperability between clients and objects, the standard marshaling infrastructure of COM+ automatically supports the ICallFactory interface in the proxy even if the object does not implement this interface. When the client calls the Begin_ method, the proxy converts this into a standard call on the synchronous version of the interface. The proxy then holds the values returned by the method until the client calls the Finish_ method to obtain the results.

Only standard marshaling provides this built-in support for mapping asynchronous client calls to synchronous calls on the object. If the object uses custom marshaling, COM+ cannot provide an automatic implementation of ICallFactory on the proxy. In addition, if the client and the object are running in the same process and are not separated by apartments or contexts, no proxy/stub code is loaded and consequently COM+ cannot provide the automatic asynchronous to synchronous translation. In such cases, the client's only recourse is to obtain a pointer to the synchronous version of the interface and make synchronous calls.

Another interesting case occurs when a client makes calls to the synchronous version of an interface on an object that implements the synchronous and asynchronous versions of that interface. In this case, the object must provide two implementations of what is fundamentally a single interface. To avoid the unnecessary duplication of functionality in the synchronous and asynchronous versions of an interface, the standard marshaling infrastructure automatically marshals synchronous client calls to the asynchronous version of the interface if they are supported by the object. Thus, the object does not have to actually implement the synchronous version of the interface so long as it provides a call object that implements the asynchronous version.

When the client makes a synchronous call on the synchronous version of the interface, the proxy in the client's address space calls the Begin_ method and blocks until the method returns. Then the proxy automatically calls the Finish_ method to obtain the results of the call before returning control to the client. This programming model greatly simplifies life for both the client and the object. The client can make asynchronous calls even if the object does not support this functionality. The object can implement only the asynchronous version of an interface that will be used to service both synchronous and asynchronous client calls.