4
\$\begingroup\$

I have an ASP.NET application which uses a Service Reference to a (third-party, offsite) payment processor.

The service reference class is generated automatically. Its implementation is a subclass of ServiceModel.ClientBase, which MSDN documents as, "Any instance members are not guaranteed to be thread safe."

  • Is the following a good implementation of my wrapper class (to be called from my aspx pages), to guarantee that access to the service reference singleton is thread-safe?
  • Should I prefer to simply make the service reference an instance member of the wrapper class, without any locking? My fear was that would imply more than one concurrent service instance, and I don't know how to test whether the remote server side would handle that successfully.

class Authorize : IDisposable
{
    private static ServiceSoapClient s_serviceSoap = null;
    private static object s_locker;

    // This method is invoked from Global.Application_Start() in Global.asax.cs
    internal static void Application_Start()
    {
        // ServiceSoapClient derives from ServiceModel.ClientBase<ServiceSoap>
        s_serviceSoap = new ServiceSoapClient();
        s_locker = new object();
    }

    private bool lockWasTaken = false;
    internal Authorize()
    {
        // Emulate C# `lock` statement
        Monitor.Enter(s_locker, ref lockWasTaken);
        // http://stackoverflow.com/questions/2008382/how-to-heal-faulted-wcf-channels
        if (s_serviceSoap.InnerChannel.State ==
           System.ServiceModel.CommunicationState.Faulted)
        {
            s_serviceSoap.Abort();
            s_serviceSoap = new ServiceSoapClient();
        }
    }
    public void Dispose()
    {
        if (lockWasTaken)
            Monitor.Exit(s_locker);
    }

    internal bool IsAlive()
    {
        // delegate to the singleton
        ANetApiResponseType response = s_serviceSoap.IsAlive();
        log(response, "IsAlive");
        return response.resultCode == MessageTypeEnum.Ok;
    }
    // plus other methods which delegate to the singleton
}
\$\endgroup\$
3
  • 1
    \$\begingroup\$ @retailcoder I don't have enough reputation to answer your question in Chat; I want them to be static because I want them to be thread-safe singletons; perhaps they could be non-static non-singletons instead? Maybe this question depends too much on hoping you have experience with framework classes? Yes the s_ prefix is ugly buy I like to know when something's static (because static is dangerous and unusual): I didn't call the instance data m_lockWasTaken. \$\endgroup\$
    – ChrisW
    Commented Jan 10, 2014 at 2:26
  • 1
    \$\begingroup\$ FYI ServiceSoapClient should also be disposed within your implementation od Dispose() as it implements IDisposable. Are you only creating one instance of your wrapper? If you are creating a new Authorize instance per asp.net request then there will be no threading issue as each instance will be within its own thread. \$\endgroup\$
    – MattC
    Commented Jan 16, 2014 at 18:05
  • \$\begingroup\$ @MattC I don't think ServiceSoapClient should also be disposed, because the singleton static ServiceSoapClient instance is expected to live for the lifetime of the application. There will be multiple instances of the Authorize wrapper (one per Page, as needed). Because there are multiple concurrent Page instances (therefore concurrent Authorize wrapper instances) there's a "threading issue" if they want concurrent access to the singleton static ServiceSoapClient instance. \$\endgroup\$
    – ChrisW
    Commented Jan 23, 2014 at 22:24

1 Answer 1

1
\$\begingroup\$

The code in the OP seems thread-safe to me, but may not be the best solution.

If the channel has many concurrent users, and if using the channel takes for example 100 msec, then 100 concurrent users would queue for up to 10 seconds before being able to use the (single) channel one after the other. A 10-second delay is unacceptable (web users abandon the session).

If the channel is to a payment processor, authorizing a customer transaction might take longer than 100 msec (not 100++ msec of CPU time for your ASP.NET server, but rather a 100++ msec delay waiting for a network response from the payment processor).

Wenlong Dong's Blog article, "Performance Improvement for WCF Client Proxy Creation in .NET 3.5 and Best Practices" (2007) says,

A common pattern of using ClientBase styled proxies is to perform creation/disposing on each call:

foreach (string msg in myList)
{
    // You may add try/catch block here ...
    HelloWorldProxy proxy = new HelloWorldProxy("MyEndpoint", new EndpointAddress(address)))
    proxy.Hello(msg);
    proxy.Close();
    // Error handling code here ...
}

This article is not ASP.NET-specific, but it states that concurrent instances of a channel are normal.

This article also warns that, creating and destroying proxies can be very expensive, gives advice on how to minimize the cost, mentions a performance improvement (implicit cacheing) made in .NET 3.5, and warns what behaviour (i.e. specifying an endpoint) will disable that cacheing.

A follow-up article titled A Sample for WCF Client Proxy Pooling gives advice on how to implement a pol of proxies.


See also here and here for warnings about how to close and dispose a WCF channel safely:

  • It should be closed before it's disposed
  • If there has been a communication error the Close could throw an exception
  • In an exception is thrown you should call Abort before trying to Dispose the channel
\$\endgroup\$
1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.