423 Stimmen

Was ist die beste Abhilfe für das WCF-Client "using" Block Problem?

Ich instanziere meine WCF-Dienst-Clients gerne innerhalb einer using Block, da dies so ziemlich die Standardmethode zur Verwendung von Ressourcen ist, die die IDisposable :

using (var client = new SomeWCFServiceClient()) 
{
    //Do something with the client 
}

Aber, wie bereits in dieser MSDN-Artikel einen WCF-Client in eine using Block könnte alle Fehler maskieren, die dazu führen, dass der Client in einem fehlerhaften Zustand verbleibt (wie eine Zeitüberschreitung oder ein Kommunikationsproblem). Langer Rede kurzer Sinn, wenn Dispose() aufgerufen wird, wird der Client Close() Methode wird ausgelöst, löst aber einen Fehler aus, weil sie sich in einem fehlerhaften Zustand befindet. Die ursprüngliche Ausnahme wird dann von der zweiten Ausnahme überdeckt. Das ist nicht gut.

Die im MSDN-Artikel vorgeschlagene Abhilfe besteht darin, die Verwendung einer using Block zu verwenden, und stattdessen Ihre Clients zu instanziieren und sie etwa so zu verwenden:

try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}

Verglichen mit dem using Block, ich finde das hässlich. Und eine Menge Code zu schreiben, jedes Mal, wenn Sie einen Client benötigen.

Glücklicherweise habe ich einige andere Umgehungsmöglichkeiten gefunden, wie z. B. diese auf dem (jetzt nicht mehr existierenden) IServiceOriented Blog. Sie beginnen mit:

public delegate void UseServiceDelegate<T>(T proxy); 

public static class Service<T> 
{ 
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 

    public static void Use(UseServiceDelegate<T> codeBlock) 
    { 
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); 
        bool success = false; 
        try 
        { 
            codeBlock((T)proxy); 
            proxy.Close(); 
            success = true; 
        } 
        finally 
        { 
            if (!success) 
            { 
                proxy.Abort(); 
            } 
        } 
     } 
} 

Das erlaubt dann:

Service<IOrderService>.Use(orderService => 
{ 
    orderService.PlaceOrder(request); 
}); 

Das ist nicht schlecht, aber ich denke nicht, dass es so ausdrucksstark und leicht verständlich ist wie die using Block.

Die Abhilfe, die ich derzeit versuche zu verwenden, habe ich zuerst auf blog.davidbarret.net . Im Grunde überschreiben Sie die Client Dispose() Methode, wo immer Sie sie verwenden. Etwa so:

public partial class SomeWCFServiceClient : IDisposable
{
    void IDisposable.Dispose() 
    {
        if (this.State == CommunicationState.Faulted) 
        {
            this.Abort();
        } 
        else 
        {
            this.Close();
        }
    }
}

Dies scheint es zu ermöglichen, die using wieder blockieren, ohne dass die Gefahr besteht, eine fehlerhafte Zustandsausnahme zu maskieren.

Gibt es noch andere Probleme, auf die ich bei der Verwendung dieser Workarounds achten muss? Ist jemandem etwas Besseres eingefallen?

8voto

pangular Punkte 699

@Marc Gravell

Wäre es nicht OK, dies zu verwenden:

public static TResult Using<T, TResult>(this T client, Func<T, TResult> work)
        where T : ICommunicationObject
{
    try
    {
        var result = work(client);

        client.Close();

        return result;
    }
    catch (Exception e)
    {
        client.Abort();

        throw;
    }
}

Oder, das Gleiche (Func<T, TResult>) im Falle von Service<IOrderService>.Use

Dies würde die Rückgabe von Variablen erleichtern.

7voto

Jesse C. Slicer Punkte 19426

Nachfolgend finden Sie eine erweiterte Version der Quelle aus die Frage und erweitert, um mehrere Kanalfabriken zwischenzuspeichern und zu versuchen, den Endpunkt in der Konfigurationsdatei anhand des Vertragsnamens zu finden.

Es verwendet .NET 4 (insbesondere: Kontravarianz, LINQ, var ) :

/// <summary>
/// Delegate type of the service method to perform.
/// </summary>
/// <param name="proxy">The service proxy.</param>
/// <typeparam name="T">The type of service to use.</typeparam>
internal delegate void UseServiceDelegate<in T>(T proxy);

/// <summary>
/// Wraps using a WCF service.
/// </summary>
/// <typeparam name="T">The type of service to use.</typeparam>
internal static class Service<T>
{
    /// <summary>
    /// A dictionary to hold looked-up endpoint names.
    /// </summary>
    private static readonly IDictionary<Type, string> cachedEndpointNames = new Dictionary<Type, string>();

    /// <summary>
    /// A dictionary to hold created channel factories.
    /// </summary>
    private static readonly IDictionary<string, ChannelFactory<T>> cachedFactories =
        new Dictionary<string, ChannelFactory<T>>();

    /// <summary>
    /// Uses the specified code block.
    /// </summary>
    /// <param name="codeBlock">The code block.</param>
    internal static void Use(UseServiceDelegate<T> codeBlock)
    {
        var factory = GetChannelFactory();
        var proxy = (IClientChannel)factory.CreateChannel();
        var success = false;

        try
        {
            using (proxy)
            {
                codeBlock((T)proxy);
            }

            success = true;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }

    /// <summary>
    /// Gets the channel factory.
    /// </summary>
    /// <returns>The channel factory.</returns>
    private static ChannelFactory<T> GetChannelFactory()
    {
        lock (cachedFactories)
        {
            var endpointName = GetEndpointName();

            if (cachedFactories.ContainsKey(endpointName))
            {
                return cachedFactories[endpointName];
            }

            var factory = new ChannelFactory<T>(endpointName);

            cachedFactories.Add(endpointName, factory);
            return factory;
        }
    }

    /// <summary>
    /// Gets the name of the endpoint.
    /// </summary>
    /// <returns>The name of the endpoint.</returns>
    private static string GetEndpointName()
    {
        var type = typeof(T);
        var fullName = type.FullName;

        lock (cachedFactories)
        {
            if (cachedEndpointNames.ContainsKey(type))
            {
                return cachedEndpointNames[type];
            }

            var serviceModel = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).SectionGroups["system.serviceModel"] as ServiceModelSectionGroup;

            if ((serviceModel != null) && !string.IsNullOrEmpty(fullName))
            {
                foreach (var endpointName in serviceModel.Client.Endpoints.Cast<ChannelEndpointElement>().Where(endpoint => fullName.EndsWith(endpoint.Contract)).Select(endpoint => endpoint.Name))
                {
                    cachedEndpointNames.Add(type, endpointName);
                    return endpointName;
                }
            }
        }

        throw new InvalidOperationException("Could not find endpoint element for type '" + fullName + "' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this name could be found in the client element.");
    }
}

7voto

makerofthings7 Punkte 57238

Was ist das?

Dies ist die CW-Version der akzeptierten Antwort, jedoch mit (meiner Meinung nach vollständiger) Behandlung von Ausnahmen.

Die akzeptierte Antwort verweist auf diese Website, die es nicht mehr gibt . Um Ihnen die Mühe zu ersparen, füge ich hier die wichtigsten Teile ein. Außerdem habe ich den Text geringfügig geändert und hinzugefügt Behandlung von Ausnahmen bei Wiederholungsversuchen um mit diesen lästigen Netzwerk-Timeouts umzugehen.

Einfache WCF-Client-Verwendung

Sobald Sie Ihren clientseitigen Proxy generiert haben, ist dies alles, was Sie zur Implementierung benötigen.

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
});

ServiceDelegate.cs

Fügen Sie diese Datei zu Ihrer Lösung hinzu. Es sind keine Änderungen an dieser Datei erforderlich, es sei denn, Sie möchten die Anzahl der Wiederholungsversuche oder die zu behandelnden Ausnahmen ändern.

public delegate void UseServiceDelegate<T>(T proxy);

public static class Service<T>
{
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 

    public static void Use(UseServiceDelegate<T> codeBlock)
    {
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
        bool success = false;

       Exception mostRecentEx = null;
       int millsecondsToSleep = 1000;

       for(int i=0; i<5; i++)  // Attempt a maximum of 5 times 
       {
           try
           {
               codeBlock((T)proxy);
               proxy.Close();
               success = true; 
               break;
           }

           // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
           catch (ChannelTerminatedException cte)
           {
              mostRecentEx = cte;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep  * (i + 1)); 
           }

           // The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
           // reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
           catch (EndpointNotFoundException enfe)
           {
              mostRecentEx = enfe;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }

           // The following exception that is thrown when a server is too busy to accept a message.
           catch (ServerTooBusyException stbe)
           {
              mostRecentEx = stbe;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch (TimeoutException timeoutEx)
           {
               mostRecentEx = timeoutEx;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           } 
           catch (CommunicationException comException)
           {
               mostRecentEx = comException;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch(Exception )
           {
                // rethrow any other exception not defined here
                // You may want to define a custom Exception class to pass information such as failure count, and failure type
                proxy.Abort();
                throw ;  
           }
       }
       if (success == false && mostRecentEx != null) 
       { 
           proxy.Abort();
           throw new Exception("WCF call failed after 5 retries.", mostRecentEx );
       }

    }
}

PS: Ich habe diesen Beitrag zu einem Gemeinschafts-Wiki gemacht. Ich werde keine "Punkte" für diese Antwort sammeln, sondern bevorzuge es, wenn Sie sie hoch bewerten, wenn Sie mit der Umsetzung einverstanden sind, oder sie bearbeiten, um sie zu verbessern.

5voto

Tomas Jansson Punkte 21651

Ein Wrapper wie dieser würde funktionieren:

public class ServiceClientWrapper<ServiceType> : IDisposable
{
    private ServiceType _channel;
    public ServiceType Channel
    {
        get { return _channel; }
    }

    private static ChannelFactory<ServiceType> _channelFactory;

    public ServiceClientWrapper()
    {
        if(_channelFactory == null)
             // Given that the endpoint name is the same as FullName of contract.
            _channelFactory = new ChannelFactory<ServiceType>(typeof(T).FullName);
        _channel = _channelFactory.CreateChannel();
        ((IChannel)_channel).Open();
    }

    public void Dispose()
    {
        try
        {
            ((IChannel)_channel).Close();
        }
        catch (Exception e)
        {
            ((IChannel)_channel).Abort();
            // TODO: Insert logging
        }
    }
}

Das sollte es Ihnen ermöglichen, Code wie diesen zu schreiben:

ResponseType response = null;
using(var clientWrapper = new ServiceClientWrapper<IService>())
{
    var request = ...
    response = clientWrapper.Channel.MyServiceCall(request);
}
// Use your response object.

Der Wrapper könnte natürlich mehr Ausnahmen abfangen, wenn dies erforderlich ist, aber das Prinzip bleibt das gleiche.

4voto

Johan Nyman Punkte 268

Verwenden Sie eine Erweiterungsmethode:

public static class CommunicationObjectExtensions
{
    public static TResult MakeSafeServiceCall<TResult, TService>(this TService client, Func<TService, TResult> method) where TService : ICommunicationObject
    {
        TResult result;

        try
        {
            result = method(client);
        }
        finally
        {
            try
            {
                client.Close();
            }
            catch (CommunicationException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (TimeoutException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (Exception)
            {
                client.Abort();
                throw;
            }
        }

        return result;
    }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X