Pergunta

Eu estou tentando escrever uma classe que encapsula WCF chamadas (o cliente é Silverlight, se isso importa). Tudo funciona às mil maravilhas, mas não tenho certeza de como armadilha uma falha de conexão, como se o servidor não responde. Parece que pouco de trabalho acontece em algum lugar no código resultante de ChannelFactory, mas eu não tenho certeza. Código Geral de revisão é bem-vinda também. :)

A linha inferior, em torno da criação do canal, ou o delegado começar ou resultado assíncrono em try / catch não faz armadilha uma falha na conexão. Eu gostaria de ter que pegar realizar o evento ServiceCallError.

public class ServiceCaller : IDisposable
{
    private IFeedService _channel;

    public ServiceCaller()
    {
        var elements = new List<BindingElement>();
        elements.Add(new BinaryMessageEncodingBindingElement());
        elements.Add(new HttpTransportBindingElement());
        var binding = new CustomBinding(elements);
        var endpointAddress = new EndpointAddress(App.GetRootUrl() + "Feed.svc");
        _channel = new ChannelFactory<IFeedService>(binding, endpointAddress).CreateChannel();
    }

    public void MakeCall(DateTime lastTime, Dictionary<string, string> context)
    {
        AsyncCallback asyncCallBack = delegate(IAsyncResult result)
        {
            var items = ((IFeedService)result.AsyncState).EndGet(result);
            if (ItemsRetrieved != null)
                ItemsRetrieved(this, new ServiceCallerEventArgs(items));
        };
        _channel.BeginGet(lastTime, context, asyncCallBack, _channel);
    }

    public event ItemsRetrievedEventHandler ItemsRetrieved;
    public event ServiceCallErrorHandler ServiceCallError;

    public delegate void ItemsRetrievedEventHandler(object sender, ServiceCallerEventArgs e);

    public delegate void ServiceCallErrorHandler(object sender, ServiceCallErrorEventArgs e);

    public void Dispose()
    {
        _channel.Close();
        _channel.Dispose();
    }
}

Aqui está o rastreamento de pilha, para aqueles que estão curiosos:

 An AsyncCallback threw an exception.
at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously, Exception exception)
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.OnGetResponse(IAsyncResult result)
   at System.Net.Browser.BrowserHttpWebRequest.<>c__DisplayClassd.<InvokeGetResponseCallback>b__b(Object state2)
   at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)

Para que isso aconteça, eu o fogo até o aplicativo no navegador, então eu matar o processo do servidor Web a partir do Visual Studio. Em um ambiente de teste, eu obter a mesma coisa por matar a conexão de rede para o sistema do cliente.

Aqui está ToString da exceção completa ():

System.Exception: An AsyncCallback threw an exception. ---> System.Exception: An AsyncCallback threw an exception. ---> System.ServiceModel.CommunicationException: The remote server returned an error: NotFound. ---> System.Net.WebException: The remote server returned an error: NotFound. ---> System.Net.WebException: The remote server returned an error: NotFound.
   at System.Net.Browser.BrowserHttpWebRequest.InternalEndGetResponse(IAsyncResult asyncResult)
   at System.Net.Browser.BrowserHttpWebRequest.<>c__DisplayClass5.<EndGetResponse>b__4(Object sendState)
   at System.Net.Browser.AsyncHelper.<>c__DisplayClass2.<BeginOnUI>b__0(Object sendState)
   --- End of inner exception stack trace ---
   at System.Net.Browser.AsyncHelper.BeginOnUI(SendOrPostCallback beginMethod, Object state)
   at System.Net.Browser.BrowserHttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.CompleteGetResponse(IAsyncResult result)
   --- End of inner exception stack trace ---
   at System.ServiceModel.Channels.Remoting.RealProxy.Invoke(Object[] args)
   at proxy_2.EndGet(IAsyncResult )
   at CoasterBuzz.Feed.Client.ServiceCaller.<MakeCall>b__0(IAsyncResult result)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   --- End of inner exception stack trace ---
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously, Exception exception)
   at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.CallComplete(Boolean completedSynchronously, Exception exception)
   at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.FinishSend(IAsyncResult result, Boolean completedSynchronously)
   at System.ServiceModel.Channels.ServiceChannel.SendAsyncResult.SendCallback(IAsyncResult result)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   --- End of inner exception stack trace ---
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously)
   at System.ServiceModel.AsyncResult.Complete(Boolean completedSynchronously, Exception exception)
   at System.ServiceModel.Channels.HttpChannelFactory.HttpRequestChannel.HttpChannelAsyncRequest.OnGetResponse(IAsyncResult result)
   at System.Net.Browser.BrowserHttpWebRequest.<>c__DisplayClassd.<InvokeGetResponseCallback>b__b(Object state2)
   at System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)

A propósito, a única maneira que eu posso pegar isso em tudo é usar nível de aplicativo evento UnhandledException no cliente SL.

Foi útil?

Solução

Jeff, eu não estou completamente certo de sua pergunta como você está simulando uma falha de conexão. Estou assumindo que "como se o servidor não responde" significa que você está à procura de algum tipo de erro de tempo limite. Você provavelmente está simulando um servidor não-responsivos, forçando uma resposta lenta de seu chamada de serviço usando Thread.Sleep () no lado do servidor. Certo? Perto o suficiente?

Eu tinha um projeto semelhante, então eu testei isso e foi capaz de interceptar um tempo limite e outras exceções com sucesso. Eu acho que talvez você não estava vendo essas exceções porque a sua ligação personalizada teve um tempo de espera muito longo padrão e você pode não ter esperado o suficiente.

Para explicar, o tempo limite do WCF são controlados pela configuração de ligação. Olhando para o seu código, você está criando ligação como esta seu próprio costume:

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

Se você definir um ponto de interrupção lá e inspecionar a ligação que criou o costume, você vai ver que tem um minuto SendTimeout por padrão. Eu suspeito que você quer não estavam esperando o tempo suficiente ou não a configuração de tempo limite simulado do seu serviço de tempo suficiente, de modo que você não foram capazes de capturar uma exceção tempo limite.

Aqui estão algumas alterações de código para demonstrar. Em primeiro lugar, definir o tempo limite na ligação:

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

Finalmente, para capturar as exceções e aumentar os eventos corretos, você pode atualizar seu delegado AsyncCallback como segue. A exceção é lançada quando EndGet () é chamado.

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);
    }
};

Tanto quanto uma revisão do código geral, eu recomendo que você evite embutir a sua configuração de ligação. em vez disso você pode declarar o seu endpoint configuração (incluindo tempos de espera razoáveis) em seu arquivo ServiceReferences.ClientConfig e nova acima de sua fábrica de canais com o nome endpoint assim:

new ChannelFactory<IFeedService>("feedServiceEndpoint");

Isso deve tornar o aplicativo mais sustentável ao longo do tempo.

Espero que isso ajude.

Jerry

Atualizado:

Jeff,

Por favor, dê uma olhada neste código e os comentários dentro para mais explicações do que está acontecendo aqui. O método makeCall () é a criação de uma função (delegado) que está a ser passada para o método BeginGet (). Essa função é executado mais tarde, quando as responde de serviço.

Por favor, faça as alterações sugeridas e definir pontos de interrupção nas linhas que começam com "AsyncCallback AsyncCallback" e "var items". Você vai ver a primeira passagem através do depurador apenas declara o código (função / delegado) para executar quando as responde de serviço e a segunda passagem é o processamento real de que a resposta, começando no interior de sua declaração delegado. Isto significa que o try externo / catch não será no escopo quando a resposta da chamada de serviço é processado.

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).
    }
}

Jerry

Outras dicas

Eu encontrei um problema semelhante antes. O principal problema é com a maneira IDisposable é implementado em instâncias do seu proxy / canal. A maneira que eu tenho resolvido é mostrado no código abaixo, onde IDirector é o meu contrato de serviço:

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;
        }
    }
}

A maneira de usar a classe acima é a seguinte:

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

Como os implementos classe ProxyWrapper IDisposable, se usarmos uma instância da classe ProxyWrapper dentro de um bloco using, o método Dispose() é garantido para ser chamado, mesmo se uma exceção é lançada. O código no método Dispose() irá lidar com todos os casos e estados do proxy / canal. Você pode então adicionar seu / logging / code delegado do evento de tratamento de erros neste método.

Leia a seguinte entrada de blog para mais informações e para uma versão mais genérica do código acima: http://bloggingabout.net/blogs/erwyn/archive/2006/12/09/WCF-Service-Proxy-Helper.aspx

Para erros de comunicação de captura com o servidor, eu sugiro colocar a chamada para .CreateChannel() em um try ... catch e lidar com coisas como CommunicationException e TimeoutExceptions lá.

Além disso, um nuggest de conselhos gerais: criar o ChannelFactory é bastante uma operação cara, de modo que você pode querer fazer isso separadamente e loja que referência à algum lugar objeto ChannelFactory

.

Se você precisa criar e, possivelmente, re-criar o canal do ChannelFactory, então você pode usar esse armazenados instância / cache do ChannelFactory uma e outra vez sem ter que criar isso o tempo todo.

Mas além disso, eu acho que você está fazendo muito bem aqui!

Marc

Seu exemplo ChannelFactory vai ser no estado "Aborted" quando esse tempo limite ocorre. Você verá que a exceção está sendo jogado não quando a exceção acontece na chamada, mas quando você chamar _channel.Dispose ().

Você vai precisar para se proteger contra essa exceção de alguma forma. A maneira mais simples seria colocar um try / catch em torno o conteúdo do seu método de descarte, mas seria melhor se você verificar o estado da instância _channel antes de tentar fechar / dispose.

É por isso que você ainda está recebendo a exceção -. Ele não está sendo tratado aqui

Update:

Com base no seu rastreamento de pilha, parece que o quadro está tentando executar o delegado de retorno de chamada e está recebendo uma exceção lá. Você respondeu em outro lugar que try / captura o conteúdo do seu delegado não resolve a questão ... o que acontece com exceções internas? Qual é o resultado completo do TheException.ToString ()?

Eu pesquei através do .NET Framework e é de fato autorizados a viajar por todo o caminho de volta para o pool de threads (pelo menos no stacktrace você tem aqui) o que causaria uma exceção não tratada e seu segmento de morrer. Se o pior vem a pior, você sempre pode assumir a rosca ... spin para cima seu próprio segmento e dentro chamar o serviço de forma síncrona, em vez de usar o padrão embutido assíncrona (a menos que este é um tipo WCF Duplex de comunicação, o que eu don' t acho que é).

Eu ainda acho que as informações de exceção completa (se houver mais alguma coisa) pode ser útil.

Jeff, por favor, mostre-nos! o código quando você tentar colocar um bloco try / catch em torno da chamada EndGet. É difícil de acreditar que não capturar uma exceção, e vendo ele vai ajudar-me acreditar.

Além disso, como parte desta experiência, se livrar do manipulador de eventos UnhandledException. Eu acho que você está pegando essa exceção, assim que o BrowserHttpRequest percebe que há um 404. Eu acho que sem o evento UnhandledException, um try / catch em torno EndGet irá capturar a exceção, se houver. Eu acho que você está pegando um estado intermediário do processo de tratamento de exceção.

Eu também gostaria de vê-lo colocar um System.Debugger.Break ou algo imediatamente após a chamada EndGet.

Estou certo em pensar que você projetou sua classe ServiceCaller de modo que você pode chamá-lo em um bloco using?

Se sim, então esse é o problema. As classes de canal WCF foram concebidos de forma a que a tornam extremamente difícil para escrever o código descartar completamente geral que atende a todas as condições de erro possíveis. Se fosse simples, então as classes de canal si seria seguro para descarte e você não precisa de uma classe wrapper.

A única maneira segura que eu encontrei até agora é encapsular não a lógica dispor, mas toda a seqüência de chamada, assim:

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();
        }
    }
}

Eu sei que é uma chamada síncrona, mas o princípio é o mesmo. Em vez de você tentando deduzir se você deve chamar Close ou Abort, a determinar com antecedência com base em quão longe através da chamada que você conseguiu chegar antes que ele caiu.

Nota - apenas o canal real precisa este tratamento especial. Você pode dispor da fábrica de qualquer maneira que você como AFAIK.

Eu acho que o problema está na maneira que você projetou chamador serviço.

O canal é aberto quando o chamador serviço é criado. Isso significa que o canal pode tempo limite e não há nada em seu código que pode recuperar a partir do timeount.

Gostaria de passar a criação e fechamento do canal com o método de fazer chamadas.

Você pode fazer um try catch em torno do início começar e em torno do conteúdo da chamada de retorno.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top