如何进行多线程安全的原子对象创建和交换

时间:2011-04-05 13:49:09

标签: c# multithreading wcf

在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考虑简化代码,我已经对此进行了重构,仍然不确定它是否完全正确。

1 个答案:

答案 0 :(得分:3)

你可以做一个Interlocked.CompareExchange,其中死信道作为比较传入,新信道作为值传入。

如果交换成功,那很好 如果交换失败,那么有人已经更新了死信道。

唯一的问题是,您偶尔会创建一个额外的通道,在您发现CompareExchange失败后,您必须抛弃该通道。在你的情况下这是否可以接受?

好处是CompareExchange可能是非阻塞的(不是100%肯定,因为我是一个Java人,而不是.Net。我的假设是基于Java的AtomicReference.compareAndSet)。