Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WTL

.NET Asynchronous Design Pattern for Native C++

4.94/5 (15 votes)
2 Nov 2010GPL318 min read 44.9K   775  
Native C++ implementation of the .NET Asynchronous Design Pattern using Windows I/O Completion Ports
,

AsyncInvokeDemo1.png

Contents

Introduction

The Code Project brings a handful of resources to the community and many, including myself, benefit from it. Since the site has been very beneficial to me, I thought it was about time I contributed something in return to the community by posting an article of my own. I wanted to post for a while but never found (or should I say, took) the time to do so. Well, here we go for my first post; please feel free to provide your feedback and questions which I will try to answer to the best of my knowledge.

The .NET Framework has greatly improved support for multithreaded applications, thereby making it easier for the programmer to create such applications. Even though the .NET Framework provides an enormous amount of tools and resources to accomplish a multitude of tasks, there are still many programmers using older libraries such as WTL/ATL/MFC or even the good old Win32 API to accomplish their tasks.

For some tasks I'm part of the latter group of programmers and I sometimes find it difficult as well as time and energy consuming to switch from using the .NET Framework. For this reason, and until C++0x brings multithreading support to a compiler near you, I thought I would re-create in C++ one of the features I use most from the .NET Framework: Asynchronous Design Patterns, in particular, the ability to invoke a function asynchronously (e.g. on a thread pool) using a delegate and then retrieve its result later.

The .NET Framework Asynchronous Design Patterns is also characterized by an interface to synchronize a function call on a specific thread (ISynchronizeInvoke) which I also included in this implementation.

Objective

The main goal of this implementation is to bring a very basic implementation of some of the .NET Asynchronous Design Patterns to the C++ language. Since I mostly use WTL/ATL when I develop native Win32 applications, I made this implementation blend well with its respective style. By no means does this implementation provide all the answers and should be considered only a starting point and a learning project for future ideas and enhancements.

Keeping in mind that dependencies are sometimes painful to manage and considering the size of this project, I made the implementation fit inside a single header file, making it very easy to include as part of an existing project. If I wasn't worried about extra dependencies, I could have used the great Boost C++ libraries to benefit from their extended (and portable) support for functors, tuples and threads, however I decided to start lean in dependencies for this tiny project. Besides, Boost wouldn't have blended well with the WTL/ATL style because of its naming convention and its heavy use of templates following the STL guidelines. The only library dependency enforced by this implementation is the use of ATL (specifically CThreadPool) which can easily be removed by re-implementing the CDelegate class to use a custom thread pool (or any other means to execute the delegates).

You should also note that the .NET Framework benefits from the CLR features, therefore forcing my implementation to simulate some behaviors using different techniques and mechanisms. Additionally, some features have just not been implemented (e.g. support for multicast delegates, variadic arguments, etc.).

More specifically here is what this implementation provides:

  • Documented source code to an example implementation of a portion of the Asynchronous Design Patterns
  • Capability to asynchronously call a global or class function and retrieve its result at a later time
  • Support for callback upon completion of a delegate asynchronous call
  • A means to detect if an asynchronous call is required and capability to synchronize that call on a specific thread
  • Automatic memory management for delegates and asynchronous resources

And doesn't provide:

  • Thread safety
  • Delegate argument type safety
  • Variadic arguments (this implementation has a pre-defined delegate signature)
  • Multicast delegates
  • Exception support
  • Error handling

Background

To better understand this article you should be familiar with asynchronous design patterns. There are many great articles about them right here on The Code Project and here are some that caught my attention:

In addition, the .NET Asynchronous Design Patterns are fully documented on the MSDN website:

Asynchronous Programming Design Patterns
http://msdn.microsoft.com/en-ca/library/ms228969.aspx

Since this implementation uses Windows I/O Completion Ports you should also consider understanding how they work. You will find relevant documentation on MSDN:

I/O Completion Ports (Windows)
http://msdn.microsoft.com/en-us/library/aa365198(VS.85).aspx

There is also use of the ATL Worker Archetype in this project to execute the delegates asynchronously through the CThreadPool class, also documented on MSDN:

ATL Server Library Reference - Worker Archetype
http://msdn.microsoft.com/en-us/library/ytkt93h8(VS.80).aspx

Proposed Implementation

The core of this implementation relies on IAsyncResult, ISynchronizeInvoke and IDelegate. IDelegate doesn't exist in .NET but I needed an interface to define a delegate, which doesn't exist in C++ (and function pointers are much lower level than delegates). I was guided by the .NET Framework for implementing IAsyncResult (which encapsulates all the resources of an asynchronous call) and created 2 classes, CAsyncResult and CThreadMethodEntry to be used respectively by CDelegate and CSynchronizeInvoke.

Let's continue with a class diagram (an image is worth a thousand words):

ClassDiagram1.png

NOTE: CMainDlg is part of the demo application, not the implementation.

The diagram should be self explanatory and the design follows some of the semantics from similar .NET classes, but here are some more details:

  • While CAsyncRequest, IDelegate and IClassDelegate could find use in an application (IDelegate more than the others), they were designed to be used internally by this implementation.
  • IAsyncResult is used for keeping a reference to an asynchronous call. It is important to keep this asynchronous call reference and call EndInvoke with it otherwise it will result in a memory leak unless it is called as a "fire & forget" type of call (further details on that topic below).
  • It is important to mention that IDelegate and ISynchronizeInvoke are closely related and both have a BeginInvoke and EndInvoke method, however their meaning is very different.

    IDelegate::BeginInvoke is used to begin an asynchronous call for the function wrapped by the delegate implementing the IDelegate interface while ISynchronizeInvoke::BeginInvoke is meant to asynchronously call the provided delegate on the thread the object implementing the ISynchronizeInvoke interface is living on (or any thread it decides to call it on for that matter). So the delegate passed to IDelegate::BeginInvoke is a callback, while the delegate passed to ISynchronizeInvoke::BeginInvoke is the delegate to call on the thread selected by the object implementing ISynchronizeInvoke.
  • IAsyncResult follows the same semantics as its .NET counterpart aside from the fact that it is missing the CompletedSynchronously getter, which didn't have a use in this implementation.
  • CAsyncResult and CThreadMethodEntry are respectively used to encapsulate the management of resources associated with an asynchronous call from a CDelegate and a CSynchronizeInvoke. CAsyncRequest is simply an abstract class implementing common portions of CAsyncResult and CThreadMethodEntry.

Is calling EndInvoke() mandatory?

Before moving forward with the interface and implementation, I would like to address the question of whether calling EndInvoke() is required or not. In the .NET implementation, if you don't call EndInvoke() on a delegate asynchronous call, it is stated that you will leak resources (documented on MSDN), but if you don't call EndInvoke() on a synchronized call (as used by WinForms), apparently no leak will occur (not officially documented).

Since the documentation of the interface states that EndInvoke() should always be called, it is up to the implementer to officially document any exceptions to the interface rules. Since no official documentation exists to state that EndInvoke() is optional in the WinForms implementation, one can assume it is required. (This still can be discussed as the WinForms implementation doesn't really follow the interface definition of the pattern.)

To get around this issue, I modified the interface definition to include an extra parameter to state whether the call is a "fire & forget" type or if EndInvoke() will be called to retrieve the return value (and free up allocated resources). In order to follow some similarity to the .NET implementation I included a default value to "fire & forget" for a synchronized invoke and not "fire & forget " for a delegate asynchronous call.

Below are the rules around the "fire & forget" scenario in this implementation:

  • For non-"fire & forget" calls, always call EndInvoke() for both delegate asynchronous and synchronized calls, otherwise resources will be leaked.
  • For "fire & forget" calls, never call EndInvoke() for delegate asynchronous or synchronized calls as the resources associated with the call have already been disposed.

Interface Details

Below are the signatures and argument details for the core functionalities:

IDelegate::BeginInvoke
IAsyncResult *BeginInvoke(ULONG_PTR ulParam = 0, IDelegate *pCallback = NULL, LPVOID pvState = NULL, BOOL bFireAndForget = FALSE)

  • ulParam - The delegate parameter; this will be passed to the function wrapped by the delegate class
  • pCallback - The callback delegate; an optional delegate that wraps a function to be called upon completing the asynchronous call. In order to simplify the use of callbacks (any resources associated with delegates) they are auto deleted upon completion. (Always use the provided delegate macros to create new delegates. See below for details)
  • pvState - An optional state associated with the asynchronous call; could be anything you want to keep in context
  • bFireAndForget - Whether this call is a "fire & forget" scenario or not.

ISynchronizeInvoke::BeginInvoke
IAsyncResult *BeginInvoke(IDelegate *pDelegate, ULONG_PTR ulParam = 0, BOOL bFireAndForget = TRUE)

  • pDelegate - The delegate wrapped function to call; this call will be made on the thread the object implementing ISynchronizeInvoke decides to make the call on, typically the thread the object implementing the interface lives on; a GUI thread for the .NET Framework implementation
  • ulParam - The delegate parameter, passed to the function wrapped by pDelegate
  • bFireAndForget - Whether this call is a "fire & forget" scenario or not.

IDelegate::EndInvoke and ISynchronizeInvoke::EndInvoke
ULONG_PTR EndInvoke(IAsyncResult **ppAsyncResult)

  • ppAsyncResult - The reference pointer of the asynchronous call to end; this has been implemented as a double pointer since the resulting pointer is set to NULL to signal that an IAsyncResult reference pointer must never be used after calling EndInvoke (as all associated resources have been deallocated).
  • The return value of EndInvoke is the return value of the function wrapped by the delegate. Clients must use a cast to recover the original type.
  • EndInvoke has the same signature for both an IDelegate or ISynchronizeInvoke. To retrieve the result of an asynchronous call one must call EndInvoke. As stated above EndInvoke must always be called to claim back allocated resources for the asynchronous call unless BeginInvoke was called as a "fire & forget" type of call. If EndInvoke is called before the asynchronous call has time to complete, it will block until the call has completed. It is also worth mentioning that callbacks are never part of an asynchronous call time frame; that is, calling EndInvoke will not block until the callback has completed. This is important because otherwise calling EndInvoke inside a callback would result in a deadlock.

    An important note about EndInvoke: Calling EndInvoke with another IAsyncResult than the one returned by the matching BeginInvoke call will result in an InvalidOperationException in the .NET version. The same rule applies to this implementation and the IAsyncResult passed to EndInvoke must be the one returned by the matching BeginInvoke. This is important as delegates use a CAsyncResult as IAsyncResult while synchronized calls use a CThreadMethodEntry as IAsyncResult.

Implementation details

Delegates

CDelegate

Implementing IDelegate is pretty straightforward, however there are some issues (you know, there always are some issues, oops, I mean challenges). First, C++'s support for variadic arguments is quite limited; I know of only va_list and the use of overloaded templates to achieve the wanted behavior. Second, function pointers are very low level and they offer no flexibility, making it difficult to adapt them in this context. (C++0x may change all of this.) After investigating many options (e.g. one could have used the Boost C++ libraries) I decided I wouldn't break my head on this and opted for a lazy fix to solve both issues: use only one argument and make the delegate signature pre-defined. The side effects are limited to:

  1. casting the argument and result back and forth, and
  2. clients are forced to use the pre-defined signature for all delegates, including callbacks (which are also delegates).
These limitations are enforced by my implementation, not by the design of the pattern.

Creating new delegates is the same as using the .NET Framework, simply pass a function pointer to the constructor. To make a class instance member function delegate, however,you will need CClassDelegate. (See below for more details.)

Since this implementation queues asynchronous delegate calls on a thread pool, the first thing a delegate must do is initialize the thread pool. This is basically done on the first asynchronous call of any delegate, however one could make the initialization function public and call it upon starting the application.

To queue a new asynchronous call, the implementation simply posts the new request on the thread pool:

IAsyncResult *BeginInvoke(ULONG_PTR ulParam = 0, IDelegate *pCallback = NULL, 
                               LPVOID pvState = NULL, BOOL bFireAndForget = FALSE)
{
    // Create a new asynchronous request for this delegate call
    CAsyncResult *pAsyncResult = new CAsyncResult(this, ulParam, pCallback, 
                                                   pvState, bFireAndForget);
    BOOL bRes = pAsyncResult->Initialize();
    ATLASSERT(bRes != FALSE);

    // Queue the new request to be executed in the thread pool
    bRes = m_cThreadPool.QueueRequest(pAsyncResult);
    ATLASSERT(bRes != FALSE);

    return pAsyncResult;
}

CDelegateWorker

The machinery behind processing queued requests lives within the ATL Worker Archetype compliant class CDelegateWorker. This is a very simple, self explanatory class where Initialize and Terminate are called once for each thread in the pool and Execute is called every time the thread is cycled. (You can read the header file atlutil.h for implementation details; it is part of ATL.)

class CDelegateWorker
{
public:
    typedef CAsyncResult * RequestType;

    BOOL Initialize(LPVOID /*pvState*/) throw()
    {
        // Thread initialization code goes here
        // An example could be calling CoInitialize() to add COM support
        // to the thread
        return TRUE;
    }

    VOID Execute(
            CAsyncResult *pAsyncResult,
            LPVOID /*pvState*/,
            LPOVERLAPPED /*pOverlapped*/)
    {
        // A request is always initialized on the calling thread to
        // initialize the wait handle before moving to the receiving
        // thread (here)
        pAsyncResult->Execute();
        pAsyncResult->Terminate();
    }

    VOID Terminate(LPVOID /*pvState*/) throw()
    {
        // Thread cleanup code goes here
        // An example could be calling CoUninitialize()
    }
};

CClassDelegate<T>

By now you may have noticed I haven't mentioned the details of passing a class member function pointer to a new CDelegate, which is another challenge ;-) . This is where CClassDelegate<T> comes into play. In order to accept a class member function pointer, CDelegate would need to use a template, which I didn't like considering the fact that it is a central class in this design implementation and would have affected many other classes because of template dependencies. Consequently I decided to create a tiny class to wrap around the details of keeping a class member function pointer and since that class uses a template, using it within CDelegate would require adding a template to CDelegate which would defeat the purpose of this class. So an interface definition IClassDelegate was created to decouple from the template dependency. The interface basically provides a mean to invoke the class member function and delete the CClassDelegate wrapper automatically. All of this means you can do something like this:

VOID SomeClass::SomeFunction()
{
    CDelegate *pDelegate = new CDelegate(new CClassDelegate<SomeClass>(this,
                                             &SomeClass::SomeOtherFunction));
    ...
}

without worrying about the new CClassDelegate<T> memory allocation. In fact, you shouldn't even worry about the memory allocation for pDelegate in a "fire & forget" scenario, since it will also be auto-deleted when its time comes. This is all very nice but creating a delegate for a class instance member function makes every declaration very lengthy so I also created some macros to help make things more concise:

VOID SomeClass::SomeFunction()
{
    MAKECLSDELEGATE(CMainDlg, SomeOtherFunction)->BeginInvoke(0, 
                              MAKECLSDELEGATE(CMainDlg, SomeCallback));
    ...
}

There are three macros to ease the creation of delegates, they are self explanatory:

// Global delegate
#define MAKEDELEGATE(MEMBER) \
    (new CDelegate(&MEMBER))
// Class delegate
#define MAKECLSDELEGATE(CLASS, MEMBER) \
    (new CDelegate(new CClassDelegate<class>(this, &CLASS::MEMBER)))
// Class instance delegate
#define MAKECLSINSTDELEGATE(CLASS, INSTANCE, MEMBER) \
    (new CDelegate(new CClassDelegate<class>(INSTANCE, &CLASS::MEMBER)))
</class></class>

CSynchronizeInvoke

For implementing ISynchronizeInvoke, I thought it would be a great opportunity to use Windows I/O Completion Ports. Even though I/O completion ports are much more useful than indicated in this implementation, they still provide an efficient and easy way of queuing a request to be processed in a different thread. This makes implementing ISynchronizeInvoke a breeze. Below is the core of queuing/processing the asynchronous calls. (Note the similarity to queuing an IDelegate asynchronous call.)

IAsyncResult *BeginInvoke(IDelegate *pDelegate, ULONG_PTR ulParam = 0, 
                          BOOL bFireAndForget = TRUE)
{
    // Create a new thread method entry for this call
    CThreadMethodEntry *pMethod = new CThreadMethodEntry(this, pDelegate, ulParam, 
                                                         bFireAndForget);
    BOOL bRes = pMethod->Initialize();
    ATLASSERT(bRes != FALSE);

    // Queue the new request for execution
    bRes = m_cPort.PostQueuedCompletionStatus(0, (ULONG_PTR)pMethod, NULL);
    ATLASSERT(bRes != FALSE);

    // Inform thread there is an asynchronous call pending
    ::PostThreadMessage(m_dwThreadId, WM_THREADCALLBACK, 0, 0);

    return pMethod;
}

Processing pending requests is done by calling ProcessPendingInvoke() which is called by IsThreadCallbackMessage from within a loop mechanism, typically a message pump (e.g. from PreTranslateMessage in WTL). By default, the function processes two asynchronous calls in order to minimize the disturbance of performing a function call on the receiving thread, potentially a GUI thread. You can pass a higher number of requests to process if you're not receiving the calls on a GUI thread.

BOOL IsThreadCallbackMessage(MSG *pMsg)
{
    if (pMsg->message == WM_THREADCALLBACK)
    {
        ProcessPendingInvoke();
        return TRUE;
    }

    return FALSE;
}

VOID ProcessPendingInvoke(DWORD dwCount = 2)
{
    if (m_cPort.GetQueuedCount() < 1 )
        return;

    DWORD dwBytes = 0;                    // Unused
    ULONG_PTR ulKey = 0;
    LPOVERLAPPED pOverlapped = NULL;      // Unused

    // Retrieve one pending call at a time up to dwCount calls
    // If no calls are queued then this loop exits immediately
    while (dwCount-- > 0 && m_cPort.GetQueuedCompletionStatus(&dwBytes, &ulKey,
                                                              &pOverlapped, 0))
    {
        CThreadMethodEntry *pMethod = reinterpret_cast<cthreadmethodentry>(ulKey);
        ATLASSERT(pMethod != NULL);

        // Execute & terminate the call
        pMethod->Execute();
        pMethod->Terminate();
    }
}
</cthreadmethodentry>

As you can see, this is pretty straightforward. Using an I/O completion port is a personal choice and one could use a different mechanism (i.e. pass pMethod with WM_THREADCALLBACK). I also looked at using an APC with QueueUserWorkItem, however it works only when the calling and receiving threads are the same, which is useless in this context.

CAsyncResult and CThreadMethodEntry

These two classes are the core of an asynchronous call; they contain all the information relating to the call such as the call wait handle, a pointer to the caller, etc. Note that their implementation was modeled to fit with CDelegateWorker's style (Initialize, Execute, Terminate).

Interestingly enough, it is mentioned in the .NET implementation that clients can cast a delegate's BeginInvoke resulting IAsyncResult to AsyncResult for accessing additional resources linked to the asynchronous call. See http://msdn.microsoft.com/en-us/library/system.runtime.remoting.messaging.asyncresult.aspx for more details. In this same manner, you can cast an IAsyncResult to CAsyncResult for delegate calls, which is very useful, especially for retrieving the delegate pointer using GetAsyncDelegate() so you can call EndInvoke() to retrieve the call's return value.

The only real challenge of creating these two classes was implementing their Terminate() member function. Once an asynchronous call has been executed it must be terminated; that is, it must check whether the call has completed, deallocate resources and, optionally, call EndInvoke() in a "fire & forget" scenario. Also, since CAsyncResult is used for a delegate asynchronous call, it is also necessary to support a callback function.

Below are the implementations of CAsyncResult and CThreadMethodEntry's Terminate() member functions respectively:

// CAsyncResult
VOID CAsyncResult::Terminate()
{
    // Flag the call as completed
    ATLASSERT(m_bIsCompleted == FALSE);
    m_bIsCompleted = TRUE;

    // Signal the call has completed
    // Signaling will delete us only if there is no callback
    // This is important because we are used as the callback
    // parameter
    ATLASSERT(m_hAsyncWaitEvent != NULL);
    BOOL bRes = ::SetEvent(m_hAsyncWaitEvent);
    ATLASSERT(bRes != FALSE);

    // Invoke the callback if one is provided
    if (m_pCallback != NULL)
    {
        m_pCallback->Invoke((ULONG_PTR)this);

        if (m_bFireAndForget)
            EndInvoke();

        // Since there was a callback EndInvoke() didn't delete
        // this request so delete it now
        delete this;
        // WARNING: Object has been deleted!

        return;
    }

    // WARNING: Object has been deleted passed EndInvoke!
    if (m_bFireAndForget)
        EndInvoke();
}

VOID CAsyncResult::EndInvoke()
{
    IAsyncResult *pAsyncResult = this;

    // Call EndInvoke, ignoring the return value
    ATLASSERT(m_pDelegate != NULL);
    m_pDelegate->EndInvoke(&pAsyncResult);
    ATLASSERT(pAsyncResult == NULL);
    // WARNING: This object is potentially deleted now!
    // Potentially meaning if m_pCallback != NULL
}

// CThreadMethodEntry
VOID CThreadMethodEntry::Terminate()
{
    // Flag the call as completed
    ATLASSERT(m_bIsCompleted == FALSE);
    m_bIsCompleted = TRUE;

    // Signal the call has completed
    // Signaling will delete us only if there is no callback
    ATLASSERT(m_hAsyncWaitEvent != NULL);
    BOOL bRes = ::SetEvent(m_hAsyncWaitEvent);
    ATLASSERT(bRes != FALSE);

    // If this is a fire and forget type of call then call
    // EndInvoke on behalf of the caller
    if (m_bFireAndForget)
    {
        IAsyncResult *pAsyncResult = this;

        // Call EndInvoke, ignoring the return value
        ATLASSERT(m_pCaller != NULL);
        m_pCaller->EndInvoke(&pAsyncResult);
        ATLASSERT(pAsyncResult == NULL);

        // WARNING: This object has now been deleted!
        // Don't access members of this object passed this point
    }
}

Using the Code

If you have used this pattern with the .NET Framework you should find it very easy to use this implementation with C++. Since the implementation takes care of delegate allocations, you are not required to keep a delegate reference pointer, which makes it even closer to the .NET implementation. Also, because of the added support for the "fire & forget" scenario, it is easy to just call a function in order to avoid blocking the current thread. However, it is very important to keep track of all the asynchronous calls made one way or another, especially during application shutdown, otherwise unexpected behavior may occur.

Asynchronous delegate calls

To call a delegate asynchronously create a CDelegate function wrapper, call BeginInvoke to start the call and EndInvoke to terminate it. Typically you would call EndInvoke from within the callback, if one is provided. You typically would create all delegates using the provided macros. So assuming you have the following member functions defined in class CMainDlg:

ULONG_PTR CMainDlg::Test1(ULONG_PTR ulParam)
{
    ...
}

ULONG_PTR CMainDlg::Test1Callback(ULONG_PTR ulParam)
{
    ...
}

You would make an asynchronous call like this:

MAKECLSDELEGATE(CMainDlg, Test1)->BeginInvoke(0, MAKECLSDELEGATE(CMainDlg, Test1Callback));

and in the callback function, you would then retrieve the function's return value:

// Retrieving Test1()'s return value
CAsyncResult *pAsyncResult = (CAsyncResult *)ulParam;
IDelegate *pDelegate = pAsyncResult->GetAsyncDelegate();
UINT nRes = (UINT)pDelegate->EndInvoke((IAsyncResult **)&pAsyncResult);
ATLASSERT(pAsyncResult == NULL);
// NEVER use the async result after calling EndInvoke!
...

For "fire & forget" scenarios, simply pass TRUE for bFireAndForget and the delegate will take all responsibility for deallocating all resources once the asynchronous call has completed:

MAKECLSDELEGATE(CMainDlg, Test1)->BeginInvoke(0, MAKECLSDELEGATE(CMainDlg, Test1Callback), 
                                                                 NULL, TRUE);
    ...
// Never call EndInvoke()

Synchronized calls

To use synchronized calls (similar to the WinForms implementation) with WTL, the first step is to derive your window class from CSynchronizeInvoke:

class CMainDlg :
    public CDialogImpl<cmaindlg>,
    public CWinDataExchange<cmaindlg>,
    public CMessageFilter,
    public CSynchronizeInvoke
{
    ...
</cmaindlg></cmaindlg>

Then, once your class has been added as a CMessageFilter, you need to check for thread callbacks as part of the message pump. This can be done be overriding PreTranslateMessage:

virtual BOOL PreTranslateMessage(MSG *pMsg)
{
    // Process thread callback messages
    if (CSynchronizeInvoke::IsThreadCallbackMessage(pMsg))
        return TRUE;
    ...

Now all the machinery to execute the synchronized calls is in place. To make a synchronized call simply call BeginInvoke:

// Inside CMainDlg
...
BeginInvoke(MAKECLSDELEGATE(CMainDlg, Test2), 0);
// No need to call EndInvoke() as synchronized calls are implemented
// as "fire & forget"

And the function Test2() will be executed on the same thread on the next iteration of the message pump loop.

Conclusion

As you can see, there are differences between this implementation and the .NET Framework's implementation for this portion of the Microsoft asynchronous design patterns. The goal was not to create an identical implementation but a similar one, to provide a very similar pattern implementation to be used in C++.

Many areas of the implementation have not been discussed, such as waiting for an asynchronous call to complete using the wait handle, polling an asynchronous call to determine whether it has finished or not, etc. Even though they are very straightforward to use, these features are worth being mentioned. Please refer to the demo application for more examples on how to use this implementation and its features. In most cases, if you have used asynchronous calls in .NET, you will find this implementation's features very easy to use since they mostly mimic the .NET implementation.

Future Ideas and Improvements

This implementation could really benefit from having error handling and exception support. Routing exceptions raised in threads is very important and should be handled by calling EndInvoke within try/catch statements as done when using .NET.

Also, this implementation provides only the basics of working asynchronously. Of course, some questions are raised such as "How can I cancel an asynchronous operation?" or "How can I report progress from the asynchronous operation?", etc. A good communication between the calling and receiving threads is very important and ISynchronizeInvoke helps in achieving good results, but its use it still quite low level. Looking forward, one may implement a BackgroundWorker class which could provide a solution to some of the questions on cancelling an asynchronous operation and progress reporting.

Points of Interest

There is plenty of information on the web regarding the Asynchronous Design Patterns, here are a few links of interest from my bookmarks list:

History

  • 23th August, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)