Здравствуйте.
Есть десктопный клиент и сервер WCF на C#, .NET4.5, TCP-соединение.
Клиент при подключении к серверу логинится, указывает номер рабочего места, имя пользователя, пароль, подписывается на события и так далее. Сейчас в случае остановки сервера клиент попросту потеряет соединение, и не подключится до тех пор, пока пользователь руками не перезапустит клиентскую программу.
Я хочу сделать так, чтобы клиент сам подключался к серверу, после того, как соединение один раз было успешно установлено. Как наилучшим образом это реализовать?
Есть еще один клиент, который переподключается сам, но там я это сделал довольно-таки сложным способом — создал класс, методы которого внутри себя используют несколько вызовов к сервису, и на каждый публичный метод есть внутренний метод-исполнитель
Выглядит это так
| Один из публичных методов, его вызывают из GUI и прочих таких мест |
| public CallResult<SideTestEvent> TestSide(int sideId)
{
return ExecuteTask(ApplyPartial<int, CancellationToken, CallResult<SideTestEvent>>(InternalTestSide, sideId), _operationTimeout);
}
|
| |
| Внутренний метод, делает всю работу с WCF-сервисом |
| public CallResult<SideTestEvent> InternalTestSide(int sideId, CancellationToken ct)
{
_logger.Debug("Worker InternalTestSide {0}", sideId);
var dispensersTask = _proxy.GetCashedDispensersAsync();
dispensersTask.Wait(ct);
if (dispensersTask.IsCompleted)
{
var dispensers = dispensersTask.Result.ToArray();
_logger.Debug("Worker InternalTestSide {0} LogDispensers", sideId);
//LogDispensers(dispensers);
var connected = dispensers.Where(_ => _.SideId == sideId && _.IsConnected).ToArray();
if (connected.Length == 0)
{
return CallResult<SideTestEvent>.Ok(new SideTestEvent {Result = SideTestResult.SideOutOfService});
}
...skipped...
}
return CallResult<SideTestEvent>.Fail();
}
|
| |
| Одна из похожих функций, принимающих на вход функцию с кучей аргументов и возвращающая ее же, с одним новым аргументом, а старые запомнены (карринг, но не совсем) |
| private static Func<T2, TResult> ApplyPartial<T1, T2, TResult>(Func<T1, T2, TResult> function, T1 arg1)
{
return (b) => function(arg1, b);
}
|
| |
| Обертка, исполняющая внутренние методы, с указанным таймаутом и перезапуском соединения в случае ошибки |
| private CallResult<T> ExecuteTask<T>(Func<CancellationToken, CallResult<T>> action, TimeSpan operationTimeout, [CallerMemberName] string callerName = "ExecuteTask")
{
CallResult<T> result;
var needReconnect = false;
if (_state == SupervisorState.Work && _proxy != null)
{
if (_proxy.State == CommunicationState.Opened)
{
using (var cts2 = CancellationTokenSource.CreateLinkedTokenSource(new[] {_cts.Token}))
{
var token = cts2.Token;
var task = Task.Factory.StartNew(() =>
{
try
{
return action(token);
}
catch (Exception exc)
{
_logger.DebugException("Service " + callerName, exc);
return CallResult<T>.Fail(exc.Message);
}
}, cts2.Token);
task.Wait((int) operationTimeout.TotalMilliseconds, cts2.Token);
if (task.IsCompleted)
{
result = task.Result;
}
else
{
cts2.Cancel();
result = CallResult<T>.Fail("Сервер не ответил за отведенное время");
needReconnect = true;
}
}
}
else
{
result = CallResult<T>.Fail("Нет связи с сервером");
needReconnect = true;
}
}
else
{
result = CallResult<T>.Fail("Нет связи с сервером");
_lastAttachError = "Нет связи с сервером";
}
if (needReconnect)
{
Reconnect();
}
return result;
}
|
| |
| Перезапуск соединения |
| private void Reconnect()
{
var iAmFirst = false;
lock (_lock)
{
if (_state != SupervisorState.Connecting)
{
iAmFirst = true;
_state = SupervisorState.Connecting;
}
}
if (iAmFirst)
{
InternalDetach();
_logger.Debug("Service Reconnect");
Task.Factory.StartNew(ConnectLoop, TaskCreationOptions.LongRunning);
}
}
|
| |
Как это работает — клиентский код что-то хочет сделать, вызывает публичный метод и за ожидаемое время гарантированно получает либо отказ, либо результат. В случае обрыва соединения из за сбоя сети, сервера или еще чего-то, будет запущен поток, пытающийся восстановить соединение, при этом все вызовы от клиентского кода в это время будут завершаться с отказом.
Проблема в том, что даже для использования в моем коде вся эта конструкция какая-то излишне громоздкая, трудоемкая. В рабочем же месте, для которого я хочу сделать нечто подобное, используется не 10 методов WCF-сервиса, а все 250. Делать ко всему этому обертки в таком стиле будет очень утомительно. Кто что может порекомендовать?
| Дополнение |
| Если кто захочет написать про быдлокод — не стесняйтесь, когда это писалось, я был мало знаком с WCF |
| |