3

we're looking into connecting Azure websites with on-premises WCF services. We want to use the Azure Relay Bus for this.

There is a cost involved in setting up the TCP channel for the first time, about 3 seconds in our case. This is understood. We do cache the channel, so a next call goes faster. ( 250ms ) The channel is cached in ThreadLocal storage.

When multiple users are using the website, for every new thread used, a new instance of a channel is created, and we have to pay the price again for setting up the TCP channel.

My question are:

How do we prevent that a real user is confronted with the delay of setting up the channel? Is it normal practice to have a TCP channel for each client thread?

Note: On-premises we use IIS auto-start or NT services to 'warm up' our WCF services, but is this the way to go for a website running on Azure? The fact that each thread has its own channel doesn't make it easier.

Code is shown below.

public static class NetTcpRelayClient<TChannel>
{
    /// <summary>
    /// A ThreadLocal instance of the channel, so that each thread can have an open TCP channel
    /// </summary>
    private static ThreadLocal<TChannel> staticChannel = new ThreadLocal<TChannel>();

    /// <summary>
    /// A shared instance of the ChannelFactory
    /// </summary>
    private static ChannelFactory<TChannel> cf = null;

    /// <summary>
    /// Creates the channel.
    /// </summary>
    /// <returns>The created channel</returns>
    private static TChannel CreateChannel()
    {
        // get the url and access parameters from the configuration service
        var address = ServiceSecurityInspector.GetNetTcpRelayBindingAddress(typeof(TContract));
        string issuerName = ConfigurationManager.AppSettings["Shared.appsettings.AzureServiceBus.IssuerName"];
        string issuerSecret = ConfigurationManager.AppSettings["Shared.appsettings.AzureServiceBus.IssuerSecret"];

        // create a NetTcpRelayBinding, 
        if (cf == null)
        {
            cf = new ChannelFactory<TChannel>(new NetTcpRelayBinding(), new EndpointAddress(address));

            cf.Endpoint.Behaviors.Add(new TransportClientEndpointBehavior
            {
                TokenProvider = TokenProvider.CreateSharedSecretTokenProvider(issuerName, issuerSecret)
            });
        }

        TChannel channel = cf.CreateChannel();

        // open the channel
        IClientChannel clientChannel = channel as IClientChannel;
        if (clientChannel != null)
        {
            clientChannel.Open();
        }

        return channel;
    }

    /// <summary>
    /// Gets the channel for making a call over the relay bus.
    /// Note that the channel is cached.
    /// And that each thread has it's own channnel.
    /// </summary>
    /// <returns>The channel</returns>
    public static TChannel GetChannel()
    {
        // check if we have a channel instance already
        if (!staticChannel.IsValueCreated)
        {
            // no, create one
            staticChannel.Value = CreateChannel();
        }
        else
        {
            // if the channel exists already
            IClientChannel clientChannel = staticChannel as IClientChannel;

            // check if it is open, if not, make a new one
            if (clientChannel != null)
            {
                CommunicationState state = clientChannel.State;

                // check its state
                if (state == CommunicationState.Faulted)
                {
                    // channel is in faulted state, close and recreate it
                    CloseChannel();

                    staticChannel.Value = CreateChannel();
                }
                else if ((state == CommunicationState.Closed) || (state == CommunicationState.Closing))
                {
                    // channel is closed or closing, recreate it
                    staticChannel.Value = CreateChannel();
                }
            }
        }

        return staticChannel.Value;
    }

    /// <summary>
    /// Closes the channel in a proper way
    /// </summary>
    private static void CloseChannel()
    {
        // always check if we still have a valid channel
        if (staticChannel != null)
        {
            IClientChannel clientChannel = staticChannel as IClientChannel;
            if (clientChannel != null)
            {
                // if the channel is open, we close it
                if (clientChannel.State != CommunicationState.Closed)
                {
                    clientChannel.Abort();
                }
            }

            // and we set the static variable back to it's default ( = null )
            staticChannel = null;
        }
    }
}

0 Answers0