В основном я пытаюсь реализовать отказоустойчивость соединения для клиентов WCF.
Я создал свой собственный MyChannelFactory
, который упаковывает WCF ChannelFactory
для работы с временными ошибками, то есть, когда служба WCF закрыта и через пару секунд снова работает. В этом случае при использовании NetNamedPipeBinding
все клиентские экземпляры, созданные с помощью var instance = channelFactory.CreateChannel()
, разрушаются, потому что он больше не работает, вам нужно снова явно создать канал, вызвав var newInstance = channelFactory.CreateChannel()
и использовать newInstance
. Одним из способов решения этой проблемы является создание оболочки интерфейса службы и в каждом методе добавьте try-catch(CommunicationException)
, создайте другой канал и попробуйте снова вызвать метод. Как видите, обязательно для создания другого экземпляра и использования этого нового экземпляра во всех методах; предыдущий экземпляр больше не подходит.
Итак, чтобы придерживаться лучших практик DynamicProxy и ограничений WCF, я успешно создал прокси (код ниже), и он работает ( производство еще не готово) но, как вы видите, я никогда не звоню invocation.Proceed()
, вместо этого я звоню invocation.Method.Invoke(newinstance,invocation.Arguments)
; Я не думаю, что это правильно, потому что, если мы добавим больше перехватчиков, это просто не будет работать. Я думаю, интерфейсный прокси с целевым интерфейсом - это путь к go, однако, как указано в руководстве , вы можете изменить цель для этого вызова, а не для всего прокси - и как я упоминал выше, необходимо изменить экземпляр канала (предыдущий сейчас бесполезен).
class MyChannelFactory<TChannel> where TChannel : class
{
private readonly ProxyGenerator generator;
private readonly ProxyGenerationOptions options;
private readonly ChannelFactory<TChannel> factory;
private readonly EndpointAddress address;
public MyChannelFactory(Binding binding, EndpointAddress address)
{
this.address = address;
factory = new ChannelFactory<TChannel>(binding);
factory.ApplyClientBehaviors();
options = new ProxyGenerationOptions { Selector = new MySelector() };
generator = new ProxyGenerator();
}
public TChannel CreateChannel()
{
return generator.CreateInterfaceProxyWithoutTarget<TChannel>(new MyInterceptor(factory, address));
}
class MyInterceptor : IInterceptor
{
private const int DefaultMaxRetryCount = 5;
private const double DefaultRandomFactor = 1.1;
private const double DefaultExponentialBase = 2;
private static readonly TimeSpan DefaultCoefficient = TimeSpan.FromSeconds(1);
private static readonly TimeSpan DefaultMaxDelay = TimeSpan.FromSeconds(30);
private readonly int maxRetryCount;
private readonly TimeSpan maxDelay;
private readonly Random random = new Random();
private readonly IChannelFactory<TChannel> factory;
private readonly EndpointAddress address;
private TChannel currentChannel;
public MyInterceptor(IChannelFactory<TChannel> factory, EndpointAddress address)
: this(DefaultMaxRetryCount, DefaultMaxDelay, factory, address) { }
public MyInterceptor(int maxRetryCount, TimeSpan maxDelay, IChannelFactory<TChannel> factory, EndpointAddress address)
{
this.maxRetryCount = maxRetryCount;
this.maxDelay = maxDelay;
this.factory = factory;
this.address = address;
}
void IInterceptor.Intercept(IInvocation invocation)
{
using (var timer = TimeExecution.Start())
{
var exceptions = new List<Exception>();
TimeSpan? delay;
while (true)
{
try
{
if (currentChannel == null)
currentChannel = factory.CreateChannel(address);
//invocation.Proceed();
invocation.Method.Invoke(currentChannel, invocation.Arguments);
break;
}
catch (TargetInvocationException ex) when (ex.InnerException is CommunicationException) //TODO: is pipe closed (only transient exceptions)
{
currentChannel = null;
exceptions.Add(ex.InnerException);
delay = GetNextDelay(exceptions);
if (delay == null)
throw new Exception($"Max retry of {maxRetryCount} exceeded in {timer.Elapsed}", ex.InnerException);
}
//catch (CommunicationException ex)
//{
// currentChannel = null;
// exceptions.Add(ex);
// delay = GetNextDelay(exceptions);
// if (delay == null)
// throw new Exception($"Max retry of {maxRetryCount} exceeded in {timer.Elapsed}", ex);
//}
Thread.Sleep(delay.Value);
}
}
}
private TimeSpan? GetNextDelay(IEnumerable<Exception> exceptions)
{
var currentRetryCount = exceptions.Count() - 1;
if (currentRetryCount < maxRetryCount)
{
var delta = (Math.Pow(DefaultExponentialBase, currentRetryCount) - 1.0)
* (1.0 + random.NextDouble() * (DefaultRandomFactor - 1.0));
var delay = Math.Min(DefaultCoefficient.TotalMilliseconds * delta, maxDelay.TotalMilliseconds);
return TimeSpan.FromMilliseconds(delay);
}
return null;
}
}
class TimeExecution : IDisposable
{
private readonly Stopwatch stopwatch;
private readonly bool enabled;
private readonly string message;
public TimeSpan Elapsed { get { return stopwatch.Elapsed; } }
public bool IsRunning { get { return stopwatch.IsRunning; } }
private TimeExecution()
{
stopwatch = new Stopwatch();
stopwatch.Start();
}
public void Dispose()
{
stopwatch.Stop();
}
public static TimeExecution Start()
{
return new TimeExecution();
}
}
class MySelector : IInterceptorSelector
{
public MySelector()
{
}
IInterceptor[] IInterceptorSelector.SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors)
{
throw new NotImplementedException();
}
}
}
Есть ли способ изменить цель для всего прокси? Или другой дизайн, который мне не хватает?