Исключение WCF получено при закрытии соединения с использованием обратных вызовов - PullRequest
6 голосов
/ 18 сентября 2010

Я использую netNamedPipeBinding для выполнения межпроцессного взаимодействия WCF между приложением Windows и службой Windows.

Теперь мое приложение работает хорошо во всех других учетных записях (после отбрасывания моей справедливой доли исключений WCF, как знал бы любой, кто работал с WCF ...), но эта ошибка оказывается весьма устойчивой.

Чтобы нарисовать картину моего сценария: моя служба Windows может быть поставлена ​​в очередь для выполнения какой-либо работы в любой момент времени с помощью кнопки, нажатой в приложении Windows, и затем она передает netNamedPipeBinding, который является привязкой, которая поддерживает обратные вызовы ( двусторонняя связь), если вы не знакомы и инициируете запрос на выполнение этой работы (в данном случае процедура загрузки файла), он также генерирует обратные вызовы (события) каждые несколько секунд, начиная от прогресса файла до скорости передачи и т. д. и т. д. и т. д. Вернемся к приложению Windows, поэтому существует довольно тесная интеграция клиент-сервер; Вот как я получаю информацию о том, что работает в моей службе Windows, обратно в мое приложение Windows.

Теперь все отлично, боги WCF сейчас относительно мной довольны, за исключением одного неприятного исключения, которое я получаю каждый раз, когда преждевременно закрываю приложение (что является вполне допустимым сценарием). Пока идет передача, а обратные вызовы запускаются довольно сильно, я получаю эту ошибку:

System.ServiceModel.ProtocolException:
  The channel received an unexpected input message with Action 
  'http://tempuri.org/ITransferServiceContract/TransferSpeedChangedCallback' 
  while closing. You should only close your channel when you are not expecting 
  any more input messages.

Теперь я понимаю эту ошибку, но, к сожалению, я не могу гарантировать закрытие своего канала после того, как больше не буду получать входные сообщения, так как пользователь может закрыть приложение в любое время, поэтому работа все равно будет продолжаться в фоновом режиме службы Windows. (вроде как работает антивирусный сканер). Пользователь должен иметь возможность без каких-либо помех запускать и закрывать приложение инструмента управления выигрышем.

Теперь об ошибке я получаю сразу после выполнения моего Unsubscribe() вызова, который является вторым последним вызовом перед завершением работы приложения, и, как я считаю, является предпочтительным способом отключения клиента WCF. Все, что отписывается перед закрытием соединения, просто удаляет идентификатор клиента из массива, который хранился локально в wcf-сервисе win-службы (так как это экземпляр SHARED, предоставляемый как службой win, так и приложением windows, так как служба win может выполнять работу в запланированные события сами по себе) и после удаления массива идентификаторов клиентов я выполняю то, что, я надеюсь (чувствую), должно быть чистым разъединением.

Результатом этого, помимо получения исключения, является зависание моего приложения, пользовательский интерфейс полностью заблокирован, индикаторы выполнения и все в середине, со всеми признаками, указывающими на наличие состояния гонки или тупик WCF [вздох], но Сейчас я достаточно разбираюсь в потоках, и я думаю, что это относительно изолированная ситуация, и я читаю исключение как есть, я не думаю, что это проблема «потока» как таковая, поскольку в ней больше говорится о проблеме раннего отключения, которая затем превращает все мои темы в хаос, возможно, вызывая блокировку.

Мой Unsubscribe() подход на клиенте выглядит следующим образом:

    public void Unsubscribe()
    {
        try
        {
            // Close existing connections
            if (channel != null &&
                channel.State == CommunicationState.Opened)
            {
                proxy.Unsubscribe();
            }
        }
        catch (Exception)
        {
            // This is where we receive the 'System.ServiceModel.ProtocolException'.
        }
        finally
        {
            Dispose();
        }
    }

И мой Dispose() метод, который должен выполнить чистое отключение:

    public void Dispose()
    {
        // Dispose object
        if (channel != null)
        {
            try
            {
                // Close existing connections
                Close();
                // Attempt dispose object
                ((IDisposable)channel).Dispose();
            }
            catch (CommunicationException)
            {
                channel.Abort();
            }
            catch (TimeoutException)
            {
                channel.Abort();
            }
            catch (Exception)
            {
                channel.Abort();
                throw;
            }
        }
    }

И служба WCF Subscription() атрибуты аналога и класса (для справки) в службе Windows сервер (здесь нет ничего сложного, и мое исключение происходит на стороне клиента):

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, 
    ConcurrencyMode = ConcurrencyMode.Multiple)] 
    public class TransferService : LoggableBase, ITransferServiceContract
    {
        public void Unsubscribe()
        {
            if (clients.ContainsKey(clientName))
            {
                lock (syncObj)
                {
                    clients.Remove(clientName);
                }
            }

#if DEBUG
            Console.WriteLine(" + {0} disconnected.", clientName);
#endif
        }
        ...
    }

Интерфейс:

[ServiceContract(
    CallbackContract = typeof(ITransferServiceCallbackContract), 
    SessionMode = SessionMode.Required)]
public interface ITransferServiceContract
{
    [OperationContract(IsInitiating = true)]
    bool Subscribe();

    [OperationContract(IsOneWay = true)]
    void Unsubscribe();
    ...
}

Интерфейс контракта обратного вызова, он не делает ничего особенного, просто вызывает события через делегатов и т. Д. Я включил это, чтобы показать вам мои атрибуты. Я уже ослабил один набор взаимоблокировок, добавив UseSynchronizationContext = false:

[CallbackBehavior(UseSynchronizationContext = false, 
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class TransferServiceCallback : ITransferServiceCallbackContract
{ ... }

Действительно надеюсь, что кто-нибудь может мне помочь! Большое спасибо =:)

1 Ответ

11 голосов
/ 18 сентября 2010

О, черт возьми, я обнаружил проблему.

Это исключение не имело никакого отношения к зависанию приложения underyling, это было всего лишь предупредительное исключение, которое вы можете безопасно поймать.

Вы быне верьте, я потратил около 6 часов на эту ошибку, оказалось, что это channel.Close() блокировка в ожидании завершения ожидающих запросов WCF (что никогда не будет завершено, пока передача не завершится!)

Я просто перебирал линию прерывания грубой силы за строкой, моя проблема была в том, что если я был слишком медленным ..... он никогда не зависал, потому что канал мог бы быть закрыт (даже до передачизакончили), поэтому мне пришлось преодолеть точку F5, а затем быстро сделать шаг, чтобы поймать зависание, и на этом все закончилось.Теперь я просто применяю значение тайм-аута к операции Close() и перехватываю его с помощью TimeoutException, а затем жестко прерываю канал, если он не может отключиться своевременно!

См. Код исправления:

private void Close()
{
    if (channel != null &&
        channel.State == CommunicationState.Opened)
    {
        // If cannot cleanly close down the app in 3 seconds,
        // channel is locked due to channel heavily in use
        // through callbacks or the like.
        // Throw TimeoutException
        channel.Close(new TimeSpan(0, 0, 0, 3));
    }
}

public void Dispose()
{
    // Dispose object
    if (channel != null)
    {
        try
        {
            // Close existing connections
            // *****************************
            // This is the close operation where we perform 
            //the channel close and timeout check and catch the exception.
            Close();

            // Attempt dispose object
            ((IDisposable)channel).Dispose();
        }
        catch (CommunicationException)
        {
            channel.Abort();
        }
        catch (TimeoutException)
        {
            channel.Abort();
        }
        catch (Exception)
        {
            channel.Abort();
            throw;
        }
    }
}

Я так счастлив, что наконец-то эта ошибка исправлена!Мое приложение теперь корректно завершает работу через 3 секунды, независимо от текущего состояния службы WCF. Надеюсь, я мог бы помочь кому-то, кто когда-либо сталкивался с подобной проблемой.

Грэм

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...