Click to Rate and Give Feedback
Related Articles

In this column, the author lays out some guiding principles that you should follow when working with the ASP.NET MVC framework.

Scott Allen

MSDN Magazine July 2009

...

Read more!

Microsoft Velocity exposes a unified, distributed memory cache for client application consumption. We show you how to add Velocity to your data-driven apps.

Aaron Dunnington

MSDN Magazine June 2009

...

Read more!

Cobra, a descendant of Python, offers a combined dynamic and statically-typed programming model, built-in unit test facilities, scripting capabilities, and much more. Feel the power here.

Ted Neward

MSDN Magazine June 2009

...

Read more!

We use the new Asynchronous Agents Library in Visual C++ 2010 to solve the classic Dining Philosophers concurrency problem.

Rick Molloy

MSDN Magazine June 2009

...

Read more!

This month's column answers frequently asked questions about implementing REST.

Jon Flanders

MSDN Magazine July 2009

...

Read more!

Also by this Author

By now, developers everywhere have had the opportunity to download the first beta of the Microsoft® . NET Framework 2. 0. ASP. NET developers who have played with it are no doubt salivating at all the cool new features.

Jeff Prosise

MSDN Magazine February 2005

...

Read more!

If you're unfamiliar with Windows Presentation Foundation (WPF), building that first Silverlight custom control can be a daunting experience. This article walks through the process.

Jeff Prosise

MSDN Magazine August 2008

...

Read more!

Now that ASP.NET 2.0 is a shipping product, it seems appropriate to revisit an issue that tops the new features wish lists of many developers: a SQL Server™ site map provider.

Jeff Prosise

MSDN Magazine February 2006

...

Read more!

Let's face it: every minute of every day, someone, somewhere, is patrolling the Web looking for sites to hack. ASP. NET developers must constantly be on their guard to ensure attempted hacks can't be successful.

Jeff Prosise

MSDN Magazine August 2004

...

Read more!

Jeff Prosise

MSDN Magazine March 2007

...

Read more!

Popular Articles

One-time passwords offer solutions to dictionary attacks, phishing, interception, and lots of other security breaches. Here's how it all works.

Dan Griffin

MSDN Magazine May 2008

...

Read more!

Here we present techniques for programmatic and declarative data binding and display with Windows Presentation Foundation.

Josh Smith

MSDN Magazine July 2008

...

Read more!

Here we introduce you to some of the concepts behind the new F# language, which combines elements of functional and object-oriented .NET languages. We then help you get started writing some simple programs.

Ted Neward

MSDN Magazine Launch 2008

...

Read more!

Ray Djajadinata

MSDN Magazine May 2007

...

Read more!

Kenny Kerr sings the praises of the new Visual C++ 2008 Feature Pack, which brings modern conveniences to Visual C++.

Kenny Kerr

MSDN Magazine May 2008

...

Read more!

Wicked Code
Running ASMX Web Services on STA Threads
Jeff Prosise

Code download available at: WickedCode2006_10.exe (152 KB)
Browse the Code Online
Many of the gnarliest issues you read about in Wicked Code come from real problems experienced by real people trying to build software that works. Recently, I came across a problem that I had never encountered before, but that I'm certain other developers have or someday will experience. The problem was one of concurrency, and had to do with ASMX Web services and legacy COM components. The development team who brought the issue to my attention had built an ASMX Web service that relied on legacy COM components written in Visual Basic® 6.0 to perform key processing tasks.
The design called for a high degree of parallelism in processing requests, so each request submitted to the Web service created its own Visual Basic component instance, and then called it. Performance was abysmal. Investigation proved that despite the number of Web service threads making concurrent calls to an equal number of Visual Basic component instances (even if there were 20 or more), only one component instance was executing at a time.
In effect, calls were being queued up at the doorway to COM and serialized into the respective component instances. Because some of the calls required 20 seconds or more to complete, the problem wasn't one the dev team could ignore. Instead, it was a showstopper—the product could not ship until the problem was rectified.
After countless hours of trying to get a handle on the problem, the dev team realized they were up against a wall. Something was causing all those concurrent calls to be executed sequentially rather than in parallel. But what? Was it COM? Was it Visual Basic 6.0? Or was it a nuance of the ASMX architecture? Whatever the problem was, a solution had to be found fast. Otherwise, the schedule would slip and the entire project would be placed in jeopardy.

The Problem
In a perfect world, everyone writing code for Microsoft platforms would write managed code and would never again have to deal with legacy technologies, such as COM. But the reality is that millions of lines of critical business logic are encapsulated in COM components—Visual Basic 6.0 COM components, in particular—and ASP.NET developers frequently have no choice but to call those components from managed code.
The Microsoft® .NET Framework does a fine job of allowing managed code to call out to unmanaged COM components. For example, the Framework's Tlbimp.exe utility can import COM type libraries and generate Runtime Callable Wrappers (RCWs) that permit unmanaged components to be called as if they were managed. But neither Tlbimp.exe, nor any other .NET tool, can mitigate the concurrency issues that arise when calls go out from managed code to unmanaged COM components.
It was a concurrency issue like this that produced the problem the dev team had encountered. Figure 1 shows the threading configuration of the Web service and the components that it has created. In this example, the Web service is processing five concurrent requests. ASMX Web services use the same HTTP pipeline that ASPX pages use, so each request has been allocated a thread by ASP.NET. ASP.NET threads are, in reality, COM multithreaded apartment (MTA) threads—that is, they're threads that run in a COM MTA. Because Visual Basic 6.0 COM components are incompatible with COM MTAs, the component instances that are created by the Web service threads are not in the MTA with their creator threads. Instead, they live in a single-threaded apartment, or STA, created by COM. The STA, like all COM STAs, is driven by a single thread. And because all five component instances share an STA, they share that single thread.
Figure 1 Default Apartment Configuration 
The fact that all five component instances run on the same thread in the same apartment explains why calls are serialized. When one of the MTA threads calls a COM component, COM marshals the call from one apartment to the other, performing a thread switch in the process. If the STA thread is busy processing a call from one MTA thread, COM continues to queue calls from other MTA threads until the STA thread becomes available. All parallelism is lost when the call crosses the boundary from the MTA to the STA because the STA can only do one thing at a time. In effect, the STA is like a big mutual exclusion lock that causes caller B to wait until the call from caller A has completed.
You can prove that Web services run on MTA threads with the simple Web service, TestService.cs, shown in Figure 2. Its one and only Web method, Test, returns a string that indicates what type of COM apartment it's running in. Figure 3 shows what happens when you invoke the Test method from ASP.NET's autogenerated test harness. The string "MTA" in the results clearly shows that the request was processed by an MTA thread.
using System;
using System.Web;
using System.Collections;
using System.Web.Services;
using System.Threading;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class TestService : System.Web.Services.WebService
{
    [WebMethod]
    public string Test()
    {
        return Thread.CurrentThread.ApartmentState.ToString();
    }
}
Figure 3 Proof of MTA Threads (Click the image for a larger view)
The scenario depicted in Figure 1 may seem contrived, but in fact it's extraordinarily common. Because ASP.NET requests run on MTA threads by default, callouts to STA-based COM objects (this includes all Visual Basic 6.0 COM components) are marshaled from the ASP.NET MTA into the COM STA. Developers often don't realize that the STA is a choke point and that seemingly concurrent calls to individual component instances are actually serialized by COM. This fact only becomes obvious when individual calls take a long time to complete.
This would be less of a problem if COM would create multiple STAs—one per MTA thread that creates an object instance. But COM doesn't do that, in part because COM is loath to spin up new threads in a process. (COM+ will do that, but COM+ is a different entity from COM.) When an MTA thread creates an STA-based object, COM is forced to create a new thread to drive that STA. When a second MTA thread creates an STA-based object, COM places that object instance in the STA it created for the first object instance. The same goes for the third object instance, fourth object instance, and so on.
Fortunately, there is a convenient solution to the COM STA bottleneck that page developers can use. While including an AspCompat="true" attribute in an ASPX's Page directive provides COM components access to ASP-style infrastructure such as Request and Response objects, it also has the very desirable effect of creating an STA thread pool and processing requests for that page with STA threads. Besides eliminating the marshaling overhead incurred by MTA-to-STA thread switches, AspCompat="true" allows STA COM objects (specifically, instances of COM classes registered ThreadingModel="Apartment") to be created in their creators' apartments provided the creators are running in STAs themselves. If there are five request processing threads running in STAs and each creates an STA COM object, then all five object instances share an apartment—and a thread—with their creator. COM is happy to place each object instance in a separate STA because it doesn't have to create the STAs (or threads to drive them). Calls to the objects are no longer queued to a single thread, and the objects can execute code concurrently.
Alas, there is no equivalent of AspCompat="true" for ASMX files. The WebService directive won't accept an AspCompat attribute. It's left to the ASMX developer to come up with a way to put ASMX requests on STA threads to avoid the STA bottleneck. Now let's look at the means for doing this.

The Solution
Before coding, I scanned a number of forums and blogs. I found a post at www.epocalipse.com/blog/category/general that proposed a solution that was both elegant and concise. It outlined an idea to write an HTTP handler that derives from System.Web.UI.Page, register it as the handler for ASMX files, and use the AspCompat infrastructure already in the Page class to lend AspCompat support to ASMX. The post was even accompanied by sample code (which was correct in principle, albeit flawed in implementation).
I built on this to produce the Page derivative shown in Figure 4. When it invokes the handler, ASP.NET calls AspCompatWebServiceHandler's BeginProcessRequest method, since AspCompatWebServiceHandler implements the IHttpAsyncHandler interface. (Without that interface, ASP.NET would call the handler's synchronous ProcessRequest method instead.) BeginProcessRequest delegates to the AspCompatBeginProcessRequest method inherited from Page. This method serves as the gateway to the AspCompat infrastructure that is built into the Page class. Shortly after AspCompat processing begins, AspCompatWebServiceHandler's OnInit method is called. This creates a normal ASMX HTTP handler (new WebServiceHandlerFactory.GetHandler) and calls its ProcessRequest method, passing in the HTTP context for the current request. Thus, the request undergoes the normal ASMX processing, but it does so within an AspCompat wrapper that processes the request with an STA thread instead of an MTA thread.
using System;
using System.Web;
using System.Web.UI;
using System.Web.Services.Protocols; 
using System.Web.SessionState; 

public class AspCompatWebServiceHandler : 
    System.Web.UI.Page, IHttpAsyncHandler, IRequiresSessionState
{ 
    protected override void OnInit(EventArgs e) 
    { 
        IHttpHandler handler =
            new WebServiceHandlerFactory ().GetHandler(
                this.Context,
                this.Context.Request.HttpMethod,
                this.Context.Request.FilePath,
                this.Context.Request.PhysicalPath);
        handler.ProcessRequest(this.Context);
        this.Context.ApplicationInstance.CompleteRequest();
    } 

    public IAsyncResult BeginProcessRequest(
        HttpContext context, AsyncCallback cb, object extraData) 
    { 
        return this.AspCompatBeginProcessRequest(
            context, cb, extraData); 
    } 

    public void EndProcessRequest(IAsyncResult result) 
    {
        this.AspCompatEndProcessRequest(result); 
    } 
}
In order for AspCompatWebServiceHandler to work its magic, it must be registered as the HTTP handler for ASMX files. You can register it using this web.config file:
<configuration>
  <system.web>
    <httpHandlers>
      <add verb="*" path="*.asmx"
        type="AspCompatWebServiceHandler, __code" />
    </httpHandlers>
  </system.web>
</configuration>
Note the assembly name used in the type attribute: __code. This assumes that you have placed the source code for AspCompatWebServiceHandler in the application's App_Code directory and allowed ASP.NET to generate an assembly from it. If, instead, you compiled the assembly yourself and place it in the bin directory, simply replace __code with the assembly name. (Since ASP.NET 1.x doesn't support App_Code directories, you'll have to do this if you're not running ASP.NET 2.0.)
In order to confirm that ASMX requests are now processed with STA threads, run TestService again after installing AspCompatWebServiceHandler. Figure 5 shows the results. The thread that ASP.NET assigned to the request is clearly an STA thread. Mission accomplished!
Figure 5 Confirming Use of STA Threads (Click the image for a larger view)
While it is a bit of a hack (there's often a very fine line between a hack and wicked code!), this solution worked flawlessly on the problem application. Figure 6 shows the threading and apartment configuration of the modified app. The five request processing threads are now STA threads living in separate STAs. Because COM places STA object instances in the STAs of their creators when the creators are STA threads, each object instance now has its own STA. Moreover, calls to the objects now execute in parallel because each object instance runs on a separate thread.
Figure 6 New Apartment Configuration 

Conclusion
Besides eliminating MTA-to-STA marshaling overhead, including the AspCompat="true" attribute allows COM to place object instances in separate STAs rather than lumping them into one apartment (and running them on one thread). Although AspCompat isn't supported for Web services, you can add it to your ASMX code and enjoy the same efficiency in calling Visual Basic 6.0 COM components from Web services as you do from Web pages.
If you'd like to learn more about COM threading and apartments, check out a pair of articles I wrote back when COM was cool. The first is available at www.codeguru.com/cpp/com-tech/activex/apts/article.php/c5529 and the second at www.codeguru.com/cpp/com-tech/activex/apts/article.php/c5533. The articles are several years old, but the content is just as pertinent today as it was back then—especially if you're a programmer who's into .NET and faced with the challenge of calling old COM components from managed code.

Send your questions and comments for Jeff to  wicked@microsoft.com.


Jeff Prosise is a contributing editor to MSDN Magazine and the author of several books, including Programming Microsoft .NET (Microsoft Press, 2002). He is also a cofounder of Wintellect, a software consulting and education firm.

Page view tracker