Handling a Transport Agent Event Asynchronously

This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

Topic Last Modified: 2008-06-24

By Saeed Noursalehi, Software Development Engineer, and Ray Dixon, Programming Writer

Handling events asynchronously is the way to go if your transport agent does any I/O, such as reading from or writing to the local disk, the Active Directory directory service, a database, and so on. This article explains when it makes sense to handle a transport agent event asynchronously and describes how to implement an asynchronous event handler in your transport agent.

Why Asynchronous?

When you implement a transport agent that handles an event, the event is invoked from the same thread pool thread that was processing the underlying session or message. The thread pool has a limited number of threads, and no thread should ever be idle so that the server can remain responsive and achieve maximum throughput.

If your agent does any I/O, for example to local disk, a database, DNS, and so on, this will potentially cause the thread that is invoking the agent to be blocked until the I/O finishes. To prevent this, you should use asynchronous I/O calls and handle the event asynchronously so that the current thread can process other messages.

Just because something is expensive doesn’t mean that you should handle it asynchronously. If the expensive operations are CPU-bound, there is no benefit to handling them asynchronously. This is especially true if you use a thread pool thread to handle the work.

In some cases, it makes sense to have a dedicated thread running in the background to which you can asynchronously schedule work from your event handlers. If you do this correctly, it can produce a net savings, especially if all that you need is a best-effort completion of the work and you can afford to discard old requests if your queue becomes too large. Generally, however, you should be very careful about adding this level of complexity.

How to Handle an Event Asynchronously

When an agent registers for an event, the event handler for the agent is invoked when the appropriate event is raised. Every event handler defines an event source and event arguments that define the operations and data that are available on that event.

Ordinarily, when the event handler returns, the following two things occur:

  • The current thread will invoke the next agent that is registered for the same event.
  • If no other agents are registered for the same event, the state for the current event is cleaned up and the server moves on to the next step after this event (for example, after the server raises the RoutingAgent.OnSubmittedMessage Event, it goes on to resolve recipients).

To prevent the server from moving on and to let the server know that the agent is still processing the current event, you must first call the GetAgentAsyncContext method from the agent base class. This method returns an object of the type AgentAsyncContext, and tells the server that this agent has decided to handle the event asynchronously.

The AgentAsyncContext object has a Complete method that the agent then has to call after it has finished all its operations for that event. After the Complete method is called, execution of that event continues. This may include calling other agents that are registered for the same event or cleaning up the state for that event.

If you do not call the GetAgentAsyncContext method, you cannot safely touch any of the event source or event arguments objects after the event handler for the event has returned. However, after you do call the GetAgentAsyncContext method, you can continue to use the event arguments objects until you call the Complete method on the AgentAsyncContext object.

Example

The following code example shows a routing agent that asynchronously handles the event and does some asynchronous I/O. The details of handling an event asynchronously are the same for all events in classes that inherit from both the SmtpReceiveAgent Class and the RoutingAgent Class.

    public class AsyncRoutingAgent : RoutingAgent
    {
        public AsyncRoutingAgent()
        {
            this.OnSubmittedMessage += new 
                SubmittedMessageEventHandler(this.SubmittedMessageHandler);
        }

        private void SubmittedMessageHandler(
            SubmittedMessageEventSource source,
            QueuedMessageEventArgs e)
        {
            // Get the async context so that the server knows 
            // that this event should be handled asynchronously.
            // Also save the event source and event args
            // so that they can be accessed from the callback.
            AsyncState<SubmittedMessageEventSource, QueuedMessageEventArgs> asyncState =
                new AsyncState<SubmittedMessageEventSource, QueuedMessageEventArgs>(
                    source,
                    e,
                    this.GetAgentAsyncContext());

            // Call an asynchronous I/O method.
            // Pass in this.AsyncIOCallback as the callback and
            // asyncState as the state argument.
        }

        private void AsyncIOCallback(IAsyncResult ar)
        {
            AsyncState<SubmittedMessageEventSource, QueuedMessageEventArgs> asyncState =
                (AsyncState<SubmittedMessageEventSource, QueuedMessageEventArgs>) ar.AsyncState;

            // Handle the results of the asynchronous I/O here.
            // The event source and event args are available from
            // the asyncState.
            // Tell the server that this event is complete.
            asyncState.Complete();

            // The event source and args can no longer be accessed.
        }

        /// <summary>
        /// This class wraps all the state that has to be saved
        /// for an asynchronous event.
        /// </summary>
        private class AsyncState<SourceType, ArgsType>
        {
            private SourceType source;
            private ArgsType args;
            private AgentAsyncContext asyncContext;

            public AsyncState(
                SourceType source,
                ArgsType args,
                AgentAsyncContext asyncContext)
            {
                this.source = source;
                this.args = args;
                this.asyncContext = asyncContext;
            }

            public SourceType Source
            {
                get { return this.source; }
            }

            public ArgsType Args
            {
                get { return this.args; }
            }

            public void Complete()
            {
                this.asyncContext.Complete();

                this.source = default(SourceType);
                this.args = default(ArgsType);
                this.asyncContext = null;
            }
        }
    }

Conclusion

Now that you know when and how to handle events asynchronously when you are using transport agents, give it a try in your own code. Try creating an event that does I/O and see what it does when you handle the event synchronously and then asynchronously. You should see a difference in server throughput and responsiveness on port 25 when it is processing a lot of messages. Give it a try!