这是关于WCF客户端池问题。 我们有一个Windows服务产生几个工作线程。 每个线程在任务上执行,并对在同一台机器上运行的服务进行WCF调用。 我们有一个客户端池逻辑(附加代码),我们将一个进行wcf调用的线程与一个由ChannelFactory.CreateChannel()创建的客户端通道相关联。
/// <summary>
/// Class used to keep track internally of which proxy is being used and which is available for the pool
/// </summary>
private class ProxyScribe
{
/// <summary>
/// Gets or sets the owner thread
/// </summary>
public Thread OwnerThread;
/// <summary>
/// Gets or sets the proxy instance
/// </summary>
public T ProxyInstance { get; set; }
}
在Acquire()方法中,我们检查池中是否有任何通道可用,如果没有线程拥有它,则重用相同的通道(代理实例)。 由于我们使用的是Interlocked.CompareExchange(),因此一次只能有一个线程使用一个通道。 如果池中没有可用的通道,我们将创建一个新通道。
在Release()方法中,我们检查被释放的通道是否实际上由记录拥有它的线程拥有。 在高负载的某些情况下,此检查失败,我们遇到以下异常。
第180行:抛出新的InvalidOperationException(“当前线程不是代理的所有者,拥有的线程ID是”+ scribe.OwnerThread.ManagedThreadId);
你觉得这个实现有什么问题吗? 我们没有使用异步来调用wcf服务。所以期望发出调用的同一个线程应该释放它。这种期望不是真的吗? 如果您有任何建议或解决方法,请与我们联系。
ClientPool.cs
/// <summary>
/// The client pool.
/// </summary>
/// <typeparam name="T">The type param.</typeparam>
/// <typeparam name="TChannel">The channel type param.</typeparam>
public abstract class ClientPool<T, TChannel> : IPool<T> where T : class where TChannel : IClientChannel, T
{
/// <summary>
/// The pool size configuration key
/// </summary>
private const string PoolSizeKey = "PoolSize";
/// <summary>
/// The upper bound for for the pool size
/// </summary>
private const int PoolSizeLimit = 1024;
/// <summary>
/// The channel factory
/// </summary>
private readonly IOcpChannelFactory<TChannel> channelFactory;
/// <summary>
/// The proxy log
/// </summary>
private readonly List<ProxyScribe> log;
/// <summary>
/// Initializes a new instance of the <see cref="ClientPool<T, TChannel>"/> class
/// </summary>
/// <param name="channelFactory">The channel factory</param>
protected ClientPool(IOcpChannelFactory<TChannel> channelFactory)
: this(
channelFactory,
int.Parse(Factory<AcmClientFactory>.Resolve<IAcmConfiguration>().GetKeyValueConfig(AcmConstants.ProxyExtensionConfigurationTypeId)[PoolSizeKey], CultureInfo.InvariantCulture))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ClientPool<T, TChannel>"/> class
/// </summary>
/// <param name="channelFactory">The channel factory</param>
/// <param name="size">The size of the pool.</param>
protected ClientPool(IOcpChannelFactory<TChannel> channelFactory, int size)
{
if (channelFactory == null)
{
throw new ArgumentNullException("channelFactory");
}
if (size < 0)
{
throw new ArgumentException("Pool size must be greater or equal to 0", "size");
}
if (size > PoolSizeLimit)
{
throw new ArgumentException("Pool size cannot exceed limit of " + PoolSizeLimit, "size");
}
this.channelFactory = channelFactory;
this.log = new List<ProxyScribe>(size);
for (int i = 0; i < size; ++i)
{
ProxyScribe scribe = new ProxyScribe();
this.log.Add(scribe);
}
}
/// <summary>
/// Gets the number of clients currently in use by the pool
/// </summary>
public int Used
{
get { return this.log.Count(x => x.OwnerThread != null); }
}
/// <summary>
/// Gets the type of the pool
/// </summary>
public string PoolType
{
get { return typeof(T).Name; }
}
/// <summary>
/// Acquires an instance
/// </summary>
/// <returns>The instance</returns>
public virtual T Acquire()
{
Thread currentThread = Thread.CurrentThread;
// go through each proxy we have in the pool and find the first available one
foreach (ProxyScribe scribe in this.log)
{
// if we can grab one from the pool, then mark it as used
if (null == Interlocked.CompareExchange(ref scribe.OwnerThread, currentThread, null))
{
try
{
scribe.ProxyInstance = this.EnsureNotFaulted(scribe.ProxyInstance);
}
catch (Exception e)
{
// we want to make sure the proxy won't get taken forever because we had an exception
scribe.OwnerThread = null;
ULS.SendTraceTag(0x6832346c /* tag_h24l */, ULSCat.ProxyExtension, ULSTraceLevel.Unexpected, "Unexpected exception occured in Acquire: {0}", e);
continue;
}
ULS.SendTraceTag(0x6832346d /* tag_h24m */, ULSCat.ProxyExtension, ULSTraceLevel.Verbose, "Acquired proxy ({0}:{1}) from the pool", this.PoolType, scribe.ProxyInstance.GetHashCode());
return scribe.ProxyInstance;
}
}
if (this.log.Count > 0)
{
// if no proxy is available in the pool, just give the caller a new instance to work with
ULS.SendTraceTag(0x6832346e /* tag_h24n */, ULSCat.ProxyExtension, ULSTraceLevel.High, "There were no available proxies on the {0} pool, creating a new instance. Current size is {1}", this.PoolType, this.log.Count);
}
return this.channelFactory.CreateChannel();
}
/// <summary>
/// Releases the specified instance
/// </summary>
/// <param name="instance">The instance</param>
public virtual void Release(T instance)
{
if (instance == null)
{
return;
}
ULS.SendTraceTag(0x6832346f /* tag_h24o */, ULSCat.ProxyExtension, ULSTraceLevel.Verbose, "Releasing proxy ({0}:{1}) to the pool", this.PoolType, instance.GetHashCode());
Thread currentThread = Thread.CurrentThread;
// go through each proxy we have in the pool and find the one used
foreach (ProxyScribe scribe in this.log)
{
// if we find it, then make sure it's clean
if (instance == scribe.ProxyInstance)
{
ULS.SendTraceTag(0x68323470 /* tag_h24p */, ULSCat.ProxyExtension, ULSTraceLevel.Verbose, "Proxy instance is part of the pool, putting it back");
// ensure the thread who requested the proxy is the one releasing it
if (scribe.OwnerThread == currentThread)
{
try
{
scribe.ProxyInstance = this.EnsureNotFaulted(scribe.ProxyInstance);
}
finally
{
// regardless if we cleaned up, release the proxy at this point
scribe.OwnerThread = null;
}
return;
}
ULS.SendTraceTag(0x68323471 /* tag_h24q */, ULSCat.ProxyExtension, ULSTraceLevel.Unexpected, "The proxy instance in the {0} pool is not owned by thread {1}, but rather {2}", this.PoolType, currentThread.ManagedThreadId, scribe.OwnerThread.ManagedThreadId);
throw new InvalidOperationException("The current thread is not the owner of the proxy, owning thread id is " + scribe.OwnerThread.ManagedThreadId);
}
}
// if the instance wasn't found on the pool it means it was a loaner
// so just dispose of it quitely and with no witnesses
ULS.SendTraceTag(0x68323472 /* tag_h24r */, ULSCat.ProxyExtension, ULSTraceLevel.Verbose, "Proxy instance was not part of the pool, disposing of it");
EnsureClosed(instance);
}
/// <summary>
/// Makes sure the given channel gets disposed of properly
/// </summary>
/// <param name="instance">The channel instance</param>
private static void EnsureClosed(T instance)
{
IClientChannel clientChannel = instance as IClientChannel;
if (clientChannel == null)
{
return;
}
if (clientChannel.State == CommunicationState.Faulted)
{
clientChannel.Abort();
}
else
{
clientChannel.Close();
}
}
/// <summary>
/// Makes sure the given channel is not faulted
/// </summary>
/// <param name="instance">The channel instance</param>
/// <returns>The proxy instance if create is true</returns>
private T EnsureNotFaulted(T instance)
{
IClientChannel clientChannel = instance as IClientChannel;
if (clientChannel != null && clientChannel.State == CommunicationState.Faulted)
{
ULS.SendTraceTag(0x68323473 /* tag_h24s */, ULSCat.ProxyExtension, ULSTraceLevel.Medium, "Proxy instance was faulted, aborting it");
clientChannel.Abort();
instance = null;
}
if (instance == null)
{
ULS.SendTraceTag(0x68323474 /* tag_h24t */, ULSCat.ProxyExtension, ULSTraceLevel.Verbose, "Proxy instance was null, creating new channel");
instance = this.channelFactory.CreateChannel();
}
return instance;
}
/// <summary>
/// Class used to keep track internally of which proxy is being used and which is available for the pool
/// </summary>
private class ProxyScribe
{
/// <summary>
/// Gets or sets the owner thread
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Needed for interlocked exchange to pass by reference")]
public Thread OwnerThread;
/// <summary>
/// Gets or sets the proxy instance
/// </summary>
public T ProxyInstance { get; set; }
}
}