WCF и переподключение
От: Слава  
Дата: 03.03.15 08:26
Оценка: 18 (2)
Здравствуйте.

Есть десктопный клиент и сервер 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
Отредактировано 03.03.2015 8:28 Слава . Предыдущая версия . Еще …
Отредактировано 03.03.2015 8:28 Слава . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.