在WCF中设置通信通道是一项有点昂贵的操作,因此建议在整个应用程序的生命周期内设置一个通信通道并共享它。一个重要的警告是,如果频道出现故障,那么该频道必须被中止和替换,这些僵尸频道无法复活。
我要做的是编写一个自定义代理,负责保持创建健康的频道并提供对其操作的访问。当通道出现故障时,粘性部分会出现,我需要以线程安全的方式将其换成新的良好通道。
这是我到目前为止的代码,当谈到线程安全编程的黑色艺术时,我仍然是一个学徒。这段代码是否过度,技术不足,只是完全错误?可以用更快,更简单或更正确的方式完成同样的事情吗?
public class WcfDeviceActivationService : IDeviceActivationService {
ChannelFactory<IDeviceActivationService> _factory = new ChannelFactory<IDeviceActivationService>();
IDeviceActivationService _channel = null;
ReaderWriterLockSlim _swaplock = new ReaderWriterLockSlim();
public WcfDeviceActivationService() {
_channel = _factory.CreateChannel();
((IClientChannel)_channel).Open();
}
public Guid ActivateDevice(string activationCode, Guid userId) {
return Call(c => c.ActivateDevice(activationCode, userId) );
}
private RT Call<RT>(Func<IDeviceActivationService, RT> chanfunc) {
try {
using(_swaplock.UseReadLock())
return chanfunc(_channel);
} catch(Exception) {
// Get a reference to the channel before attempting the exclusive lock
var chan = _channel;
// Take an exclusive lock to block callers while we check the channel
// (just to prevent excessive failures under heavy call load)
using (_swaplock.UseWriteLock()) {
// Let's see if we're still working with the original channel and if it's faulted
if (Object.ReferenceEquals(chan, _channel) && ((IClientChannel)chan).State == CommunicationState.Faulted) {
// It faulted, so lets create a new channel to replace the bad one
// If the channel creation throws, the next attempt to use the failed channel
// will take this path again and attempt to create a fresh channel.
// We want the creation exception to propagate so that the caller
// knows that their call failed because the channel couldn't be created.
var newchan = _factory.CreateChannel();
((IClientChannel)newchan).Open();
// Exchange the new channel for the old one
// (assigning reference types is atomic, but Exchange also does a memory barrier for us)
Interlocked.Exchange(ref _channel, newchan);
// Clean up the old channel
((IClientChannel)chan).Abort();
}
}
// Propagate exception to the caller
throw;
}
}
}
更新
通过Fredrik和mlaw考虑简化代码,我已经对此进行了重构,仍然不确定它是否完全正确。
答案 0 :(得分:3)
你可以做一个Interlocked.CompareExchange,其中死信道作为比较传入,新信道作为值传入。
如果交换成功,那很好 如果交换失败,那么有人已经更新了死信道。
唯一的问题是,您偶尔会创建一个额外的通道,在您发现CompareExchange失败后,您必须抛弃该通道。在你的情况下这是否可以接受?
好处是CompareExchange可能是非阻塞的(不是100%肯定,因为我是一个Java人,而不是.Net。我的假设是基于Java的AtomicReference.compareAndSet)。