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?

1voto

InteXX Punkte 6026

Für diejenigen, die es interessiert, hier eine VB.NET-Übersetzung der akzeptierten Antwort (unten). Ich habe sie der Kürze halber ein wenig verfeinert und einige der Tipps von anderen in diesem Thread kombiniert.

Ich gebe zu, es ist off-topic für die ursprünglichen Tags (C#), aber da ich nicht in der Lage war, eine VB.NET-Version dieser feinen Lösung zu finden, gehe ich davon aus, dass andere auch suchen werden. Die Lambda-Übersetzung kann etwas knifflig sein, also möchte ich jemandem die Mühe ersparen.

Beachten Sie, dass diese spezielle Implementierung die Möglichkeit bietet, die ServiceEndpoint zur Laufzeit.


Code:

Namespace Service
  Public NotInheritable Class Disposable(Of T)
    Public Shared ChannelFactory As New ChannelFactory(Of T)(Service)

    Public Shared Sub Use(Execute As Action(Of T))
      Dim oProxy As IClientChannel

      oProxy = ChannelFactory.CreateChannel

      Try
        Execute(oProxy)
        oProxy.Close()

      Catch
        oProxy.Abort()
        Throw

      End Try
    End Sub

    Public Shared Function Use(Of TResult)(Execute As Func(Of T, TResult)) As TResult
      Dim oProxy As IClientChannel

      oProxy = ChannelFactory.CreateChannel

      Try
        Use = Execute(oProxy)
        oProxy.Close()

      Catch
        oProxy.Abort()
        Throw

      End Try
    End Function

    Public Shared ReadOnly Property Service As ServiceEndpoint
      Get
        Return New ServiceEndpoint(
          ContractDescription.GetContract(
            GetType(T),
            GetType(Action(Of T))),
          New BasicHttpBinding,
          New EndpointAddress(Utils.WcfUri.ToString))
      End Get
    End Property
  End Class
End Namespace

Verwendung:

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Disposable(Of IService).Use(Sub(Client) Jobs = Client.GetJobs(Me.Status))
  End Get
End Property

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Return Disposable(Of IService).Use(Function(Client) Client.GetJobs(Me.Status))
  End Get
End Property

1voto

CodingWithSpike Punkte 41513

Unsere Systemarchitektur verwendet häufig die Einigkeit IoC Framework, um Instanzen von ClientBase zu erstellen, so dass es keine sichere Methode gibt, um zu erzwingen, dass die anderen Entwickler überhaupt die using{} Blöcke. Um es so narrensicher wie möglich zu machen, habe ich diese benutzerdefinierte Klasse erstellt, die ClientBase erweitert und das Schließen des Kanals bei Dispose oder Finalize behandelt, falls jemand die von Unity erstellte Instanz nicht explizit entsorgt.

Es gibt auch Sachen, die im Konstruktor getan werden müssen, um den Kanal für benutzerdefinierte Anmeldeinformationen und so einzurichten, also ist das auch hier drin...

public abstract class PFServer2ServerClientBase<TChannel> : ClientBase<TChannel>, IDisposable where TChannel : class
{
    private bool disposed = false;

    public PFServer2ServerClientBase()
    {
        // Copy information from custom identity into credentials, and other channel setup...
    }

    ~PFServer2ServerClientBase()
    {
        this.Dispose(false);
    }

    void IDisposable.Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    public void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            try
            {
                    if (this.State == CommunicationState.Opened)
                        this.Close();
            }
            finally
            {
                if (this.State == CommunicationState.Faulted)
                    this.Abort();
            }
            this.disposed = true;
        }
    }
}

Dann kann ein Kunde einfach:

internal class TestClient : PFServer2ServerClientBase<ITest>, ITest
{
    public string TestMethod(int value)
    {
        return base.Channel.TestMethod(value);
    }
}

Und der Anrufer kann dies alles tun:

public SomeClass
{
    [Dependency]
    public ITest test { get; set; }

    // Not the best, but should still work due to finalizer.
    public string Method1(int value)
    {
        return this.test.TestMethod(value);
    }

    // The good way to do it
    public string Method2(int value)
    {
        using(ITest t = unityContainer.Resolve<ITest>())
        {
            return t.TestMethod(value);
        }
    }
}

1voto

Andriy Buday Punkte 1909
public static class Service<TChannel>
{
    public static ChannelFactory<TChannel> ChannelFactory = new ChannelFactory<TChannel>("*");

    public static TReturn Use<TReturn>(Func<TChannel,TReturn> codeBlock)
    {
        var proxy = (IClientChannel)ChannelFactory.CreateChannel();
        var success = false;
        try
        {
            var result = codeBlock((TChannel)proxy);
            proxy.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }
}

Damit lassen sich Return-Anweisungen sehr gut schreiben:

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

1voto

Ufuk Hacıoğulları Punkte 36960

Ich habe geschrieben eine einfache Basisklasse die dies übernimmt. Es ist verfügbar als NuGet-Paket und es ist recht einfach zu bedienen.

//MemberServiceClient is the class generated by SvcUtil
public class MemberServiceManager : ServiceClientBase<MemberServiceClient>
{
    public User GetUser(int userId)
    {
        return PerformServiceOperation(client => client.GetUser(userId));
    }

    //you can also check if any error occured if you can't throw exceptions       
    public bool TryGetUser(int userId, out User user)
    {
        return TryPerformServiceOperation(c => c.GetUser(userId), out user);
    }
}

0voto

Joe Punkte 117971

Ich habe meinen eigenen Wrapper für einen Kanal, der Dispose wie folgt implementiert:

public void Dispose()
{
        try
        {
            if (channel.State == CommunicationState.Faulted)
            {
                channel.Abort();
            }
            else
            {
                channel.Close();
            }
        }
        catch (CommunicationException)
        {
            channel.Abort();
        }
        catch (TimeoutException)
        {
            channel.Abort();
        }
        catch (Exception)
        {
            channel.Abort();
            throw;
        }
}

Dies scheint gut zu funktionieren und ermöglicht die Verwendung eines Blocks.

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