Где захватить неудавшееся соединение на WCF вызов класса?

А виртуальный базовый класс является классом, который нельзя инстанцировать: Вы не можете создать прямое дополнение из него.

я думаю, что Вы путаете две совсем других вещи. Виртуальное наследование не является тем же самым как абстрактным классом. Виртуальное наследование изменяет поведение вызовов функции; иногда это разрешает вызовы функции, которые иначе были бы неоднозначны, иногда это задерживает обработку вызова функции к классу, кроме которого ожидал бы в невиртуальном наследовании.

5
задан John Saunders 5 August 2009 в 01:59
поделиться

7 ответов

Джефф, из вашего вопроса я не совсем уверен, как вы имитируете сбой соединения. Я предполагаю, что «как будто сервер не отвечает» означает, что вы ищете какую-то ошибку тайм-аута. Вероятно, вы имитируете неотвечающий сервер, заставляя медленный ответ на вызов службы с помощью Thread.Sleep () на стороне сервера. Правильно? Достаточно близко?

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

Чтобы объяснить, тайм-ауты WCF контролируются конфигурацией привязки. Глядя на свой код, вы создаете свою собственную привязку следующим образом:

var elements = new List<BindingElement>();
elements.Add(new BinaryMessageEncodingBindingElement());
elements.Add(new HttpTransportBindingElement());
var binding = new CustomBinding(elements);

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

Вот некоторые изменения кода для демонстрации. Во-первых, чтобы установить тайм-аут привязки:

var binding = new CustomBinding(elements);
//Set the timeout to something reasonable for your service
//This will fail very quickly
binding.SendTimeout = TimeSpan.FromSeconds(1);

Наконец, чтобы перехватить исключения и вызвать правильные события, вы можете обновить делегат AsyncCallback следующим образом. Исключение возникает при вызове EndGet ().

AsyncCallback asyncCallBack = delegate(IAsyncResult result)
{
    IFeedService service = ((IFeedService)result.AsyncState);
    try
    {
        var items = service.EndGet(result);
        ItemsRetrieved(this, EventArgs.Empty);
    }
    catch (System.TimeoutException ex)
    {
        //Handles timeout
        ServiceCallError(this, EventArgs.Empty);
    }
    catch (System.ServiceModel.CommunicationException ex)
    {
        //Handles a number of failures:
        //  Lack of cross-domain policy on target site
        //  Exception thrown by a service
        ServiceCallError(this, EventArgs.Empty);
    }
    catch (System.Exception ex)
    {
        //Handles any other errors here
        ServiceCallError(this, EventArgs.Empty);
    }
};

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

new ChannelFactory<IFeedService>("feedServiceEndpoint");

Это должно со временем сделать приложение более удобным.

Надеюсь, это поможет.

Jerry

Обновлено:

Jeff,

Пожалуйста, взгляните на этот код и комментарии в нем для более подробного объяснения того, что здесь происходит. Метод MakeCall () создает функцию (делегат), которая передается методу BeginGet (). Эта функция позже выполняется, когда служба отвечает.

Пожалуйста, внесите предложенные изменения и установите точки останова в строках, начинающихся с «AsyncCallback asyncCallback» и «var items». Вы увидите, что первый проход через отладчик просто объявляет код (функцию / делегат) для выполнения, когда служба отвечает, а второй проход - это фактическая обработка этого ответа, начиная с внутренней части объявления вашего делегата. Это означает, что внешняя попытка / перехват будет вне области видимости, когда будет обработан ответ на вызов службы.

public void MakeCall(DateTime lastTime, Dictionary<string, string> context)
{
    try
    {
        AsyncCallback asyncCallBack = delegate(IAsyncResult result)
        {
            try
            {
                var items = ((IFeedService)result.AsyncState).EndGet(result);
                if (ItemsRetrieved != null)
                    ItemsRetrieved(this, new ServiceCallerEventArgs(items));
            }
            catch (Exception ex)
            { 
                //This will catch errors from the service call
            }
        };
        _channel.BeginGet(lastTime, context, asyncCallBack, _channel);
    }
    catch(Exception ex)
    {
        //This will not catch an error coming back from the service.  It will 
        //catch only errors in the act of calling the service asynchronously.

        //The communication with the service and response is handled on a different
        //thread, so this try/catch will be out of scope when that executes.

        //So, at this point in the execution, you have declared a delegate 
        //(actually an anonymous delegate the compiler will turn into a hidden class) 
        //which describes some code that will be executed when the service responds.

        //You can see this in action by setting a breakpoint just inside the delegate
        //at the line starting with "var items".  It will not be hit until the service
        // responds (or doesn't respond in a timeout situation).
    }
}

Джерри

8
ответ дан 13 December 2019 в 05:39
поделиться

Ваш экземпляр ChannelFactory будет находиться в состоянии «Прервано», когда произойдет этот тайм-аут. Вы обнаружите, что исключение генерируется не тогда, когда исключение происходит в вызове, а когда вы вызываете _channel.Dispose ().

Вам нужно как-то защитить себя от этого исключения. Самый простой способ - это попытаться / уловить содержимое вашего метода удаления, но было бы лучше, если бы вы проверили состояние экземпляра _channel, прежде чем пытаться закрыть / удалить.

Вот почему вы все еще получаете исключение - здесь оно не обрабатывается.

Обновление:

Судя по трассировке стека, похоже, что фреймворк пытается выполнить ваш делегат обратного вызова и получает исключение. В другом месте вы ответили, что попытка / перехват содержимого вашего делегата не решает проблему ... как насчет внутренних исключений? Каков полный вывод TheException.ToString ()?

Я пробежался через .NET Framework, и ему действительно разрешено возвращаться в пул потоков (по крайней мере, в имеющейся у вас трассировке стека), что могло бы вызвать необработанное исключение и ваш поток умирает.

0
ответ дан 13 December 2019 в 05:39
поделиться

Я уже сталкивался с подобной проблемой раньше. Основная проблема заключается в том, как IDisposable реализован на экземплярах вашего прокси / канала. Способ, которым я решил эту проблему, показан в приведенном ниже коде, где IDirector - это мой контракт на обслуживание:

public class ProxyWrapper : IDisposable
{
    private IDirector proxy;
    private ChannelFactory<IDirector> factory;
    int callCount = 0;

    public ProxyWrapper()
    {
        factory = new ChannelFactory<IDirector>();

        proxy = factory.CreateChannel();
    }

    public IDirector Proxy
    {
        get
        {
            if (callCount > 0)
                throw new InvalidOperationException("The proxy can only be accessed once for every ProxyWrapper instance. You must create a new ProxyWrapper instance for each service request.");
            // only allow the proxy/channel to be used for one call.

            callCount++;
            return proxy;
        }
    }

    public void Dispose()
    {
        IClientChannel channel = (IClientChannel)proxy;

        try
        {
            if (channel.State != CommunicationState.Faulted)
            {
                channel.Close();
            }
            else
            {
                channel.Abort();
            }
        }
        catch (CommunicationException)
        {
            channel.Abort();
        }
        catch (TimeoutException)
        {
            channel.Abort();
        }
        catch (Exception)
        {
            channel.Abort();
            throw;
        }
        finally
        {
            channel = null;
            proxy = null;
        }
    }
}

Способ использования вышеуказанного класса следующий:

    public static void Login(string userName, string password)
    {
        using (ProxyWrapper wrapper = new ProxyWrapper())
        {
            currentSession = wrapper.Proxy.Login(userName, password);
        }
    }

Поскольку ProxyWrapper класс реализует IDisposable , если мы используем экземпляр класса ProxyWrapper внутри с использованием блока , метод Dispose () гарантированно вызывается, даже если возникает исключение. Код в методе Dispose () будет обрабатывать все случаи и состояния прокси / канала. Затем вы можете добавить в этот метод код делегата обработки ошибок / ведения журнала / событий.

Прочтите следующую запись в блоге для получения дополнительной информации и более общей версии приведенного выше кода: http://bloggingabout.net/blogs/erwyn/archive/2006/12/09/WCF-Service- Proxy-Helper.aspx

4
ответ дан 13 December 2019 в 05:39
поделиться

Чтобы выявить ошибки связи с сервером, я бы предложил позвонить по адресу .CreateChannel () в попытке ... поймать и обработать такие вещи, как CommunicationException и TimeoutExceptions.

Кроме того, один самый главный совет: создание ChannelFactory - довольно дорогостоящая операция, поэтому вы можете захотеть сделайте это отдельно и сохраните эту ссылку на объект ChannelFactory где-нибудь.

Если вам нужно создать и, возможно, повторно создать канал из ChannelFactory, вы можете затем снова и снова использовать этот сохраненный / кэшированный экземпляр ChannelFactory, не имея создавать это все время.

Но в остальном, я думаю, у вас здесь все хорошо!

Marc

0
ответ дан 13 December 2019 в 05:39
поделиться

Джефф, пожалуйста, покажите нам ваш код, когда вы пытаетесь поместить блок try / catch вокруг вызова EndGet. Трудно поверить, что это не перехватывает исключение, и это поможет мне поверить.

Кроме того, как часть этого эксперимента, избавьтесь от обработчика события UnhandledException. Я думаю, вы поймаете это исключение, как только BrowserHttpRequest поймет, что есть 404. Я думаю, что без события UnhandledException попытка / уловка EndGet перехватит исключение, если оно есть. Я думаю, вы выбрали промежуточное состояние процесса обработки исключений.

Я также хотел бы, чтобы вы поместили System.Debugger.Break или что-то еще сразу после вызова EndGet.

0
ответ дан 13 December 2019 в 05:39
поделиться

Прав ли я, думая, что вы создали свой класс ServiceCaller так, чтобы его можно было вызывать в с помощью блока ?

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

Единственный безопасный способ, который я нашел на сегодняшний день, - это инкапсулировать не логику удаления, а всю последовательность вызовов , таким образом:

public static void Invoke<TContract>(ChannelFactory<TContract> factory, Action<TContract> action) where TContract : class {

    var proxy = (IClientChannel) factory.CreateChannel();
    bool success = false;
    try {
        action((TContract) proxy);
        proxy.Close();
        success = true;
    } finally {
        if(!success) {
            proxy.Abort();
        }
    }
}

Я знаю, что это синхронный вызов, но принцип тот же. Вместо того, чтобы пытаться определить, следует ли вызывать Close или Abort , вы определяете его заранее, исходя из того, как далеко вам удалось пройти через вызов, прежде чем он упал.

Примечание - только реальный канал нуждается в этой специальной обработке. Вы можете избавиться от фабрики любым удобным для вас способом, AFAIK.

0
ответ дан 13 December 2019 в 05:39
поделиться

Я думаю, что проблема в том, как вы спроектировали вызывающую службу.

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

Я бы перенес создание и закрытие канала в метод вызова make.

Вы можете попытаться поймать начало и вокруг содержимого обратного вызова.

0
ответ дан 13 December 2019 в 05:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: