我们正在研究将 Azure 网站与本地 WCF 服务连接起来。我们想为此使用 Azure 中继总线。
第一次建立 TCP 通道是有成本的,在我们的例子中大约是 3 秒。这是可以理解的。我们确实缓存了频道,因此下一个调用会更快。( 250ms ) 通道缓存在 ThreadLocal 存储中。
当多个用户使用网站时,每使用一个新线程,就会创建一个新的通道实例,我们不得不再次为建立 TCP 通道付出代价。
我的问题是:
我们如何防止真正的用户面临建立频道的延迟?每个客户端线程都有一个 TCP 通道是正常的做法吗?
注意:在本地,我们使用 IIS 自动启动或 NT 服务来“预热”我们的 WCF 服务,但这是在 Azure 上运行网站的方式吗?每个线程都有自己的通道这一事实并没有让它变得更容易。
代码如下所示。
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;
}
}
}