Frage

Ich versuche, eine Klasse zu schreiben, die WCF kapselt nennt (der Client ist Silverlight, wenn es darauf ankommt). Es funktioniert alles swimmingly, aber ich bin nicht sicher, wie ein Verbindungsfehler zu stoppen, als wenn der Server nicht reagiert. Es wäre die wenig Arbeit erscheint irgendwo in dem resultierenden Code von Channel passiert, aber ich bin nicht sicher. Allgemeiner Code-Review ist auch willkommen. :)

Unterm Strich, die Schaffung des Kanals umgibt, oder beginnen oder Asynchron-Ergebnis Delegierten in try / catch nicht Falle einer fehlgeschlagenen Verbindung. Ich möchte, dass Fang haben die ServiceCallError Ereignis ausgeführt werden.

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

Hier ist der Stack-Trace, für diejenigen, die neugierig sind:

 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)

Um dies zu ermöglichen, habe ich die App im Browser anwerfen, dann töte ich den Web-Server-Prozess von Visual Studio. In einer Testumgebung, bekomme ich die gleiche Sache durch die Netzwerkverbindung für das Client-System zu töten.

Hier ist die vollständige Ausnahme ToString ():

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)

übrigens, der einzige Weg, ich dies überhaupt fangen ist App-Ebene UnhandledException Ereignis in dem SL-Client zu verwenden.

War es hilfreich?

Lösung

Jeff, ich bin nicht ganz sicher aus Ihrer Frage, wie Sie einen Verbindungsfehler simulieren. Ich gehe davon aus, dass „als ob der Server nicht reagiert“ bedeutet, dass Sie für eine Art von Timeout-Fehlern suchen. Sie sind wahrscheinlich ein nicht reagierendes Server simuliert durch eine langsame Antwort von Ihrem Service-Aufruf zu zwingen mit Thread.Sleep () auf der Serverseite. Richtig? Es liegt nahe genug?

Ich hatte ein ähnliches Projekt, so dass ich das getestet und war eine Zeitüberschreitung und andere Ausnahmen zu stoppen erfolgreich in der Lage. Ich denke, vielleicht waren Sie nicht, diese Ausnahmen zu sehen, weil Ihre benutzerdefinierten ein sehr langes Standard-Timeout hatte Bindung und Sie können nicht lange genug gewartet haben.

Um zu erklären, sind WCF Timeouts durch die Bindung Konfiguration gesteuert. Mit Blick auf den Code, die Sie Ihre eigenen benutzerdefinierten Bindung wie folgt zu erstellen:

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

Wenn Sie einen Haltepunkt dort einstellen und überprüfen Sie die benutzerdefinierte Bindung Sie erstellt haben, werden Sie sehen, es eine Minute hat Sendtimeout standardmäßig. Ich vermute, Sie waren entweder nicht lange genug warten oder nicht Ihr Dienstes des simulierte Timeout lange genug einstellen, so dass Sie nicht in der Lage waren, eine Timeout-Ausnahme zu fangen.

Hier sind einige Änderungen am Code zu demonstrieren. Erstens, um den Timeout auf der Bindung:

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

Schließlich, um die Ausnahmen zu fangen und die richtigen Ereignisse auslösen, können Sie Ihre AsyncCallback Delegierten wie folgt aktualisieren. Die Ausnahme wird ausgelöst, wenn EndGet () aufgerufen wird.

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

Was als allgemeiner Code-Review, ich würde empfehlen, dass Sie zu vermeiden, hart codieren Ihre Bindungskonfiguration. Sie können Ihre Endpunktkonfiguration (einschließlich angemessener Timeouts) in Ihrer ServiceReferences.ClientConfig Datei und neu Sie Ihre Kanalfactory mit dem Endpunktnamen wie folgt stattdessen deklarieren:

new ChannelFactory<IFeedService>("feedServiceEndpoint");

Das sollte die App mehr wartbar im Laufe der Zeit machen.

Ich hoffe, das hilft.

Jerry

Aktualisiert:

Jeff,

Bitte nehmen Sie sich einen Blick auf diesen Code und die Kommentare in für mehr Erklärung dessen, was hier geschieht. Die Makecall () Methode ist die Schaffung eine Funktion (delegieren), die den BeginGet () Methode übergeben wird. Diese Funktion wird später ausgeführt wird, wenn der Dienst reagiert.

Bitte stellen Sie die vorgeschlagenen Änderungen und Haltepunkte auf den Linien mit „AsyncCallback AsyncCallback“ ab und „var items“. Sie werden der erste Durchlauf durch den Debugger sehen lediglich den Code deklariert (Funktion / Delegate) auszuführen, wenn der Dienst reagiert und der zweite Durchgang ist die tatsächliche Verarbeitung dieser Reaktion, die an der Innenseite Ihres Delegatendeklaration beginnen. Dies bedeutet, dass die äußere try / catch in ihrem Umfang nicht sein wird, wenn die Antwort des Serviceabrufs verarbeitet wird.

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

Andere Tipps

Ich habe ein ähnliches Problem vor. Das Hauptproblem ist die Art und Weise IDisposable auf Instanzen Ihres Proxy / Kanal implementiert ist. So wie ich es gelöst habe, ist in dem unten stehenden Code gezeigt, wo IDirector mein Servicevertrag ist:

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

Die Art und Weise die obige Klasse zu verwenden, ist wie folgt:

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

Da die ProxyWrapper Klasse IDisposable implementiert, wenn wir eine Instanz der Klasse ProxyWrapper innerhalb eines using Block verwenden, wird die Dispose() Methode garantiert aufgerufen werden, auch wenn eine Ausnahme ausgelöst wird. Der Code in der Dispose() Methode werden alle Fälle und Zustände des Proxy / Kanal handhaben. Sie können Ihre Fehlerbehandlung / logging / Ereignisdelegaten Code in dieser Methode dann hinzuzufügen.

Lesen Sie den folgenden Blog-Eintrag für weitere Informationen und für eine generische Version des Codes oben: http://bloggingabout.net/blogs/erwyn/archive/2006/12/09/WCF-Service-Proxy-Helper.aspx

Um Kommunikationsfehler mit dem Server zu fangen, würde ich vorschlagen, den Anruf setzt in ein try ... catch .CreateChannel() und Griff Dinge wie Communication und TimeoutExceptions es.

, auch ein Nuggest der allgemeinen Beratung:. Schaffung des Channel ist eine recht teuere Operation, so dass Sie diese separat tun mögen und speichern, dass der Bezug auf das Objekt irgendwo Channel

Wenn Sie erstellen müssen und möglicherweise den Kanal aus dem Channel neu erstellen, können Sie dann verwenden Sie diese gespeichert / gecached Instanz des Channel immer und immer wieder, ohne dass die ganzen Zeit erstellen zu müssen.

Aber anders als das, würde ich denken, Sie tun hier ganz gut!

Marc

Ihr Channel Instanz wird in dem „Abgebrochen“ Zustand befindet, wenn dieser Timeout auftritt. Sie werden feststellen, dass die Ausnahme nicht ausgelöst wird, wenn die Ausnahme in dem Aufruf geschieht, aber wenn Sie anrufen _channel.Dispose ().

Sie werden gegen diese Ausnahme schützen müssen irgendwie. Der einfachste Weg, um einen try / catch um den Inhalt Ihrer dispose-Methode zu setzen wäre, aber es wäre besser, wenn Sie den Zustand der _channel Instanz überprüft, bevor Sie versuchen / dispose zu schließen.

Dies ist, warum Sie immer noch die Ausnahme bekommen -. Es ist nicht hier behandelt zu werden

Update:

Basierend auf Ihrem Stack-Trace, es sieht aus wie der Rahmen Ihren Callback-Delegaten versucht, auszuführen und eine Ausnahme wird immer. Sie reagierte woanders versuchen, dass / die Inhalte Ihrer Delegierten zu kontrollieren löst nicht das Problem ... was innere Ausnahmen? Was ist die volle Leistung von TheException.ToString ()?

I gesteuert durch das .NET Framework und es ist in der Tat des ganzen Weg zurück in den Threadpool (zumindest in dem stacktrace Du hier) zu reisen erlaubt, die eine nicht behandelte Ausnahme und den Thread verursachen würden zu sterben. Wenn alle Stricke reißen, kann man immer das Einfädeln übernehmen ... Spin Ihren eigenen Thread und innen rufen Sie den Dienst synchron, anstatt das builtin async Muster mit (es sei denn, dies ist ein WCF Duplex Art der Kommunikation, die ich don‘ t denke, es ist).

Ich denke immer noch die vollen Ausnahmeinformationen (wenn es etwas mehr ist) nützlich sein könnte.

Jeff uns bitte zeigen Ihr Code, wenn Sie versuchen, um den EndGet Anruf einen try / catch-Block zu setzen. Es ist schwer zu glauben, dass eine Ausnahme nicht verfängt, und zu sehen, es wird helfen, glauben Sie mir.

Auch dieses Experiment als Teil des UnhandledException Ereignishandler loszuwerden. Ich glaube, Sie diese Ausnahme sind fangen, sobald die BrowserHttpRequest realisiert gibt es einen 404. Ich glaube, dass es ohne die UnhandledException Ereignis, ein try / catch um EndGet wird die Ausnahme abfangen, falls vorhanden. Ich glaube, Sie Kommissionierung einen Zwischenzustand des Ausnahmebehandlung Prozess.

Ich würde auch gerne ein System.Debugger.Break oder etwas unmittelbar nach dem EndGet Aufruf setzen, um zu sehen.

Bin ich recht in der Annahme, dass Sie Ihre ServiceCaller Klasse entworfen haben, so dass Sie es in einem using Block anrufen können?

Wenn ja, dann ist das das Problem. Die WCF-Kanalklassen wurden so gestaltet, wie es extrem schwierig machen Entsorgung Code zu schreiben, ganz allgemein, die für alle möglichen Fehlerbedingungen bietet. Wenn es einfach wäre, dann werden die Kanalklassen würden sich sicher zu entsorgen und Sie würden nicht eine Wrapper-Klasse benötigen.

Der einzig sichere Weg, die ich bisher gefunden habe, ist nicht die dispose Logik kapseln, sondern die gesamte Aufrufsequenz, also:

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

Ich weiß, das ist ein synchroner Aufruf, aber das Prinzip ist das gleiche. Statt Ihnen, ob Sie Close oder Abort sollte rufen abzuleiten versuchen, bestimmen Sie es im Voraus auf, wie weit durch den Anruf Sie es geschafft, bevor es umfiel.

Hinweis - nur der tatsächliche Kanal benötigt diese spezielle Behandlung. Sie können Sie AFAIK wie jede mögliche Weise der Fabrik entsorgen.

Ich denke, dass das Problem in der Art und Weise ist, dass Sie Service Anrufer entworfen haben.

Der Kanal geöffnet wird, wenn Service Anrufer erstellt wird. Dies bedeutet, dass der Kanal Timeout kann und es gibt nichts in Ihrem Code, der von der timeount erholen kann.

Ich würde die Schaffung und Schließen des Kanals zu der Make Call-Methode bewegen.

Sie können einen Versuch fangen tun um die beginnen zu bekommen und um die Inhalte des Rückrufs.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top