WCF 客户端“使用”块问题的最佳解决方法是什么?

What is the best workaround for the WCF client `using` block issue?

提问人:Eric King 提问时间:2/22/2009 最后编辑:Eric King 更新时间:12/17/2020 访问量:117577

问:

我喜欢在块中实例化我的 WCF 服务客户端,因为它几乎是使用实现以下资源的标准方法:usingIDisposable

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

但是,正如这篇 MSDN 文章中所述,将 WCF 客户端包装在块中可能会掩盖导致客户端处于故障状态(如超时或通信问题)的任何错误。长话短说,调用时,客户端的方法会触发,但会引发错误,因为它处于错误状态。然后,原始异常被第二个异常屏蔽。不好。usingDispose()Close()

MSDN 文章中建议的解决方法是完全避免使用块,而是实例化客户端并使用它们,如下所示:using

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

与街区相比,我认为这很丑陋。每次需要客户端时,都要编写大量代码。using

幸运的是,我找到了其他一些解决方法,例如(现已不复存在)IServiceOriented 博客上的这个解决方法。你从以下几点开始:

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

然后允许:

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

这还不错,但我认为它不像块那样富有表现力和易于理解。using

我目前正在尝试使用的解决方法是我首先在 blog.davidbarret.net 上读到的。基本上,无论您在哪里使用它,您都会覆盖客户端的方法。像这样:Dispose()

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

这似乎能够再次允许该块,而不会掩盖故障状态异常的危险。using

那么,使用这些解决方法,我还需要注意其他问题吗?有人想出更好的办法吗?

使用 WCF-Client vb.net WCF C#

评论

44赞 Brian 2/22/2009
最后一个(检查这个。State)是一个种族;当您检查布尔值时,它可能不会出错,但在调用 Close() 时可能会出错。
16赞 Brian 4/9/2010
你读状态;它没有错。在调用 Close() 之前,通道会出错。Close() 抛出。游戏结束。
4赞 Eric King 4/10/2010
时间流逝。这可能是一个非常短的时间段,但从技术上讲,在检查通道状态和要求其关闭之间的时间段内,通道的状态可能会发生变化。
9赞 hIpPy 3/23/2013
我会用 .次要。Action<T>UseServiceDelegate<T>
2赞 Fabio Marreco 10/17/2014
我真的不喜欢这个静态助手,因为它使单元测试复杂化(就像大多数静态的东西一样)。我希望它是非静态的,这样它就可以注入到使用它的类中。Service<T>

答:

34赞 MichaelGG 2/22/2009 #1

我编写了一个高阶函数来使其正常工作。我们已经在几个项目中使用了它,它似乎效果很好。这就是从一开始就应该做的事情,没有“使用”范式等。

TReturn UseService<TChannel, TReturn>(Func<TChannel, TReturn> code)
{
    var chanFactory = GetCachedFactory<TChannel>();
    TChannel channel = chanFactory.CreateChannel();
    bool error = true;
    try {
        TReturn result = code(channel);
        ((IClientChannel)channel).Close();
        error = false;
        return result;
    }
    finally {
        if (error) {
            ((IClientChannel)channel).Abort();
        }
    }
}

您可以像这样拨打电话:

int a = 1;
int b = 2;
int sum = UseService((ICalculator calc) => calc.Add(a, b));
Console.WriteLine(sum);

这与示例中的示例非常相似。在某些项目中,我们编写强类型帮助程序方法,因此我们最终会编写类似“Wcf.UseFooService(f=>f...)”之类的内容。

我觉得它很优雅,所有的事情都考虑到了。您是否遇到过特定问题?

这允许插入其他漂亮的功能。例如,在一个站点上,该站点代表登录用户向服务进行身份验证。(该站点本身没有凭据。通过编写我们自己的“UseService”方法助手,我们可以按照我们想要的方式配置通道工厂,等等。我们也不拘泥于使用生成的代理——任何接口都可以。

评论

0赞 Marshall 3/23/2018
我遇到异常:ChannelFactory.Endpoint 上的 Address 属性为 null。ChannelFactory 的 Endpoint 必须指定有效的地址。什么是方法?GetCachedFactory
0赞 Medinoc 4/11/2022
缓存通道工厂对我来说听起来很奇怪,因为当通道出现故障时,工厂也会出错(试图处理它也会抛出)!CommunicationObjectFaultedException
142赞 Marc Gravell 2/22/2009 #2

实际上,虽然我写了博客(见 Luke 的回答),但我认为这比我的 IDisposable 包装器要好。典型代码:

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

(根据评论进行编辑)

由于返回 void,处理返回值的最简单方法是通过捕获的变量:Use

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated

评论

2赞 Anttu 3/21/2012
@MarcGravell 我可以在哪里注入该客户端?我假设 ChannelFactory 创建客户端,并且工厂对象在 Service 类中更新,这意味着应该对代码进行一些重构以允许自定义工厂。这是正确的,还是我在这里遗漏了一些明显的东西?
17赞 chris 8/14/2014
您可以轻松修改包装器,因此不需要捕获变量来获取结果。像这样的东西:public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
3赞 PreguntonCojoneroCabrón 9/30/2015
也许有用和https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/https://devzone.channeladam.com/articles/2014/09/how-to-easily-call-wcf-service-properly/http://dzimchuk.net/post/wcf-error-helpers
0赞 Hippasus 10/24/2015
如何使用这种方式添加凭据?
4赞 Kiquenet 10/14/2016
在我看来,最正确的解决方案是:1) 在没有竞争条件的情况下执行 Close/Abort 模式 2) 处理服务操作引发异常的情况 3) 处理 Close 和 Abort 方法都引发异常的情况 4) 处理异步异常,例如 ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
1赞 CodingWithSpike 2/24/2009 #3

我们的系统架构通常使用 Unity IoC 框架来创建 ClientBase 的实例,因此没有确定的方法来强制其他开发人员甚至使用块。为了使其尽可能万无一失,我创建了这个自定义类,它扩展了 ClientBase,并在 dispose 或 finalize 时处理关闭通道,以防有人没有显式释放 Unity 创建的实例。using{}

还需要在构造函数中完成一些事情来设置自定义凭据和其他东西的通道,所以这也在这里......

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

然后,客户端可以简单地:

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

调用方可以执行以下任一操作:

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

评论

0赞 CaffGeek 5/19/2010
从不在 Dispose 方法中使用参数 dispose
0赞 CodingWithSpike 5/20/2010
@Chad - 我遵循Microsoft常见的 Finalize/Dispose 设计模式: msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx 不过,我确实没有使用该变量,因为我不需要在正常处置和最终确定之间进行任何不同的清理。它可以被重写为只让 Finalize 调用 Dispose() 并将代码从 Dispose(bool) 移动到 Dispose()。
0赞 TrueWill 8/25/2012
终结器会增加开销,并且不是确定性的。我尽可能避免它们。您可以使用 Unity 的自动工厂来注入委托并将其放入使用块中,或者(更好地)将创建/调用/处置服务行为隐藏在注入接口上的方法后面。对依赖项的每次调用都会创建代理,调用代理,并释放代理。
92赞 Matt Davis 9/15/2009 #4

如果在 IServiceOriented.com 倡导的解决方案和 David Barret 的博客倡导的解决方案之间进行选择,我更喜欢通过重写客户端的 Dispose() 方法提供的简单性。这允许我继续使用 using() 语句,就像人们期望使用一次性对象一样。但是,正如@Brian所指出的,此解决方案包含一个争用条件,即在检查状态时,状态可能不会出错,但在调用 Close() 时可能会出错,在这种情况下,CommunicationException 仍会发生。

因此,为了解决这个问题,我采用了一种融合了两全其美的解决方案。

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}

评论

2赞 Zack Jannsen 10/30/2013
对非托管资源使用“Try-Finally”(或语法糖 - “using(){}”)语句不是有风险吗?举个例子,如果“关闭”选项失败,则不会捕获异常,最终可能不会运行。此外,如果 finally 语句中存在异常,它可以屏蔽其他异常。我认为这就是首选 Try-Catch 的原因。
0赞 Patrick Szalapski 7/22/2014
扎克,不清楚你的对象;我错过了什么?如果 Close 方法引发异常,则 finally 块将在引发异常之前执行。右?
1赞 Matt Davis 9/12/2014
@jmoreno,我撤消了你的编辑。如果你注意到了,该方法中根本没有 catch 块。这个想法是,任何发生的异常(即使是在最后)都应该被抛出,而不是默默地抓住。
5赞 Konstantin Spirin 12/23/2014
@MattDavis 为什么需要旗帜?为什么不呢?successtry { Close(); } catch { Abort(); throw; }
0赞 goku_da_master 5/5/2016
放一个尝试/捕捉怎么样?如果我可以在 finally 块中成功中止它,我不希望抛出异常。在这种情况下,我只希望在 Abort() 失败时抛出异常。这样,try/catch 将隐藏潜在的争用条件异常,并且仍然允许您在 finally 块中中止 () 连接。Close(); success = true;
14赞 Neil 1/22/2010 #5

我终于找到了一些坚实的步骤,以彻底解决这个问题。

此自定义工具扩展了 WCFProxyGenerator 以提供异常处理代理。它生成一个名为 继承 - 后者实现代理功能的实质。结果是,您可以选择使用默认代理,该代理继承或封装管理通道工厂和通道的生存期。ExceptionHandlingProxy 遵循您在“添加服务引用”对话框中有关异步方法和集合类型的选择。ExceptionHandlingProxy<T>ExceptionHandlingProxyBase<T>ClientBase<T>ExceptionHandlingProxy<T>

Codeplex 有一个名为“异常处理 WCF 代理生成器”的项目。它基本上在 Visual Studio 2008 中安装一个新的自定义工具,然后使用此工具生成新的服务代理(添加服务引用)。它有一些不错的功能来处理故障通道、超时和安全处置。这里有一个很好的视频,叫做 ExceptionHandlingProxyWrapper,解释了它是如何工作的。

您可以安全地再次使用该语句,如果通道在任何请求(TimeoutException 或 CommunicationException)上出错,包装器将重新初始化出错的通道并重试查询。如果失败,它将调用命令并释放代理并重新引发异常。如果服务抛出代码,它将停止执行,并且代理将安全地中止,并按预期抛出正确的异常。UsingAbort()FaultException

评论

0赞 Kiquenet 10/14/2016
@Shimmy状态测试版。日期:2009 年 7 月 11 日星期六作者:Michele Bustamante。死亡项目?
5赞 Tomas Jansson 12/7/2010 #6

像这样的包装器可以工作:

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

这应该使您能够编写如下代码:

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

如果需要,包装器当然可以捕获更多异常,但原则保持不变。

评论

0赞 makerofthings7 5/26/2011
我记得关于在某些情况下不调用 Dispose 的讨论......导致 WCF 的内存泄漏。
0赞 Tomas Jansson 5/26/2011
我不确定这是否会导致内存泄漏,但问题就在于此。当您调用 IChannel 时,如果通道处于故障状态,它可能会引发异常,这是一个问题,因为 Microsoft 指定永远不应该抛出。因此,上面的代码所做的是在抛出异常时处理这种情况。如果投掷,可能是严重的错误。去年12月,我写了一篇关于它的博客文章:blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapperDisposeDisposeCloseAbort
30赞 makerofthings7 2/19/2011 #7

这是 Microsoft 推荐的处理 WCF 客户端调用的方法:

有关详细信息,请参阅:预期异常

try
{
    ...
    double result = client.Add(value1, value2);
    ...
    client.Close();
}
catch (TimeoutException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}
catch (CommunicationException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}

其他信息:似乎很多人都在WCF上问这个问题,以至于Microsoft甚至创建了一个专门的示例来演示如何处理异常:

c:\WF_WCF_Samples\WCF\Basic\Client\ExpectedExceptions\CS\client

下载示例:C#VB

考虑到涉及 using 语句的问题太多了,(热?关于这个问题的内部讨论和线程,我不会浪费时间试图成为一个代码牛仔并找到更干净的方法。我将吸取它,并以这种冗长(但受信任)的方式为我的服务器应用程序实现 WCF 客户端。

可选的其他捕获失败

许多异常源自,我认为大多数异常都不应该重试。我在MSDN上仔细研究了每个异常,并找到了一个可重试的异常的简短列表(除了上面的异常)。如果我错过了应该重试的异常,请告诉我。CommunicationExceptionTimeOutException

  // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
catch (ChannelTerminatedException cte)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// 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)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// The following exception that is thrown when a server is too busy to accept a message.
catch (ServerTooBusyException stbe)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

诚然,这是一段平凡的代码。我目前更喜欢这个答案,并且在该代码中没有看到任何可能导致问题出现的“黑客”。

评论

1赞 janv8000 1/8/2014
示例中的代码是否仍会导致问题?我尝试运行UsingUseing项目(VS2013),但仍在执行行..."Hope this code wasn't important, because it might not happen."
7赞 Jesse C. Slicer 8/31/2011 #8

下面是问题中源代码的增强版本,并扩展为缓存多个通道工厂,并尝试按合约名称在配置文件中查找端点。

它使用 .NET 4(特别是:逆变、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.");
    }
}

评论

1赞 Mike Mayer 3/30/2013
为什么用代替?UseServiceDelegate<T>Action<T>
1赞 Jesse C. Slicer 3/30/2013
我认为原作者这样做的唯一原因是有一个强类型的委托,开发人员会知道该委托属于调用服务。但是,据我所知,效果也一样好。Action<T>
4赞 Luiz Felipe 9/8/2011 #9

如果你不需要 IoC 或者正在使用自动生成的客户端(服务参考),那么你可以简单地使用包装器来管理关闭,并让 GC 在客户端处于不会抛出任何异常的安全状态时获取客户端库。GC 将在 serviceclient 中调用 Dispose,这将调用 .由于它是全封闭的,因此不会造成任何损坏。我在生产代码中使用它没有问题。Close

public class AutoCloseWcf : IDisposable
{

    private ICommunicationObject CommunicationObject;

    public AutoDisconnect(ICommunicationObject CommunicationObject)
    {
        this.CommunicationObject = CommunicationObject;
    }

    public void Dispose()
    {
        if (CommunicationObject == null)
            return;
        try {
            if (CommunicationObject.State != CommunicationState.Faulted) {
                CommunicationObject.Close();
            } else {
                CommunicationObject.Abort();
            }
        } catch (CommunicationException ce) {
            CommunicationObject.Abort();
        } catch (TimeoutException toe) {
            CommunicationObject.Abort();
        } catch (Exception e) {
            CommunicationObject.Abort();
            //Perhaps log this

        } finally {
            CommunicationObject = null;
        }
    }
}

然后,当您访问服务器时,您可以创建客户端并在 autodisconect 中使用:using

var Ws = new ServiceClient("netTcpEndPointName");
using (new AutoCloseWcf(Ws)) {

    Ws.Open();

    Ws.Test();
}
4赞 Jay Douglass 1/6/2012 #10

我使用 Castle 动态代理解决了 Dispose() 问题,并且还实现了在频道处于不可用状态时自动刷新频道。若要使用此功能,必须创建一个继承服务协定和 IDisposable 的新接口。动态代理实现此接口并包装 WCF 通道:

Func<object> createChannel = () =>
    ChannelFactory<IHelloWorldService>
        .CreateChannel(new NetTcpBinding(), new EndpointAddress(uri));
var factory = new WcfProxyFactory();
var proxy = factory.Create<IDisposableHelloWorldService>(createChannel);
proxy.HelloWorld();

我喜欢这样,因为您可以注入 WCF 服务,而消费者无需担心 WCF 的任何细节。而且没有像其他解决方案那样的额外麻烦。

看一下代码,其实很简单: WCF 动态代理

7赞 9 revs, 2 users 99%LamonteCristo #11

这是怎麽?

这是已接受答案的 CW 版本,但包含(我认为完整的)异常处理。

公认的答案引用了这个不再存在的网站。为了省去您的麻烦,我在这里包括最相关的部分。此外,我还对其进行了轻微修改,以包含异常重试处理,以处理那些讨厌的网络超时。

简单的 WCF 客户端用法

生成客户端代理后,这就是实现它所需的全部内容。

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

服务委托.cs

将此文件添加到解决方案中。不需要对此文件进行任何更改,除非您要更改重试次数或要处理的异常。

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:我已经把这篇文章变成了一个社区维基。我不会从这个答案中收集“分数”,但如果你同意实现,我希望你投票,或者编辑它以使其更好。

评论

0赞 John Saunders 3/13/2012
我不确定我是否同意你对这个答案的描述。它是 CW 版本,添加了异常处理的想法
0赞 makerofthings7 3/13/2012
@JohnSaunders - True(我的异常处理概念)。让我知道我遗漏或处理不当的任何异常。
0赞 Kiquenet 11/12/2013
什么是成功变量?它需要添加到源代码中:if (success) return; ??
0赞 Bart Calixto 1/29/2014
如果第一次调用引发并且第二次调用成功,则 mostRecentEx 不会为 null,因此您引发的异常无论如何都失败了 5 次重试。还是我错过了什么?如果在第 2、3、4 或 5 次尝试成功,我看不到您在哪里清除了 mostRecentEx。也看不到成功的回报。我应该在这里遗漏一些东西,但是如果没有抛出异常,此代码不会始终运行 5 次?
0赞 makerofthings7 1/29/2014
@Bart - 我添加到了最终的 if 语句中success == false
11赞 TrueWill 8/25/2012 #12

根据 Marc Gravell、MichaelGG 和 Matt Davis 的回答,我们的开发人员提出了以下建议:

public static class UsingServiceClient
{
    public static void Do<TClient>(TClient client, Action<TClient> execute)
        where TClient : class, ICommunicationObject
    {
        try
        {
            execute(client);
        }
        finally
        {
            client.DisposeSafely();
        }
    }

    public static void DisposeSafely(this ICommunicationObject client)
    {
        if (client == null)
        {
            return;
        }

        bool success = false;

        try
        {
            if (client.State != CommunicationState.Faulted)
            {
                client.Close();
                success = true;
            }
        }
        finally
        {
            if (!success)
            {
                client.Abort();
            }
        }
    }
}

使用示例:

string result = string.Empty;

UsingServiceClient.Do(
    new MyServiceClient(),
    client =>
    result = client.GetServiceResult(parameters));

它尽可能接近“使用”语法,在调用 void 方法时不必返回虚拟值,并且可以对服务进行多次调用(并返回多个值),而无需使用元组。

此外,如果需要,您可以将其与后代一起使用,而不是 ChannelFactory。ClientBase<T>

如果开发人员想要手动释放代理/通道,则会公开扩展方法。

评论

0赞 sll 11/13/2012
如果我使用的是 PoolingDuplex 并且在调用后不关闭连接,那么使用它是有意义的,所以我的客户服务可能会存活几天并处理服务器回调。据我了解,这里讨论的解决方案对每个会话一次调用有意义吗?
0赞 TrueWill 11/14/2012
@sll - 用于在呼叫返回后立即关闭连接(每个会话一个呼叫)。
0赞 TrueWill 4/17/2015
@cacho 私有化当然是一种选择,可以避免混淆。可能有些用例有人想直接调用它,但我不能随便想出一个。DisposeSafely
0赞 Cacho Santa 4/17/2015
@truewill只是为了文档,值得一提的是,这种方法是线程安全的,对吧?
1赞 Kiquenet 10/14/2016
在我看来,最正确的解决方案是:1) 在没有竞争条件的情况下执行 Close/Abort 模式 2) 处理服务操作引发异常的情况 3) 处理 Close 和 Abort 方法都引发异常的情况 4) 处理异步异常,例如 ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
1赞 Ufuk Hacıoğulları 1/16/2013 #13

我编写了一个简单的基类来处理这个问题。它以 NuGet 包的形式提供,并且非常易于使用。

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

评论

0赞 Kiquenet 10/29/2014
VS2013-.net 4.5.1 有什么更新吗?重试的任何选项都像 stackoverflow.com/a/9370880/206730 一样?–
0赞 Ufuk Hacıoğulları 10/29/2014
@Kiquenet 我不再从事 WCF 工作了。如果您向我发送拉取请求,我可以合并它并更新包。
8赞 pangular 5/2/2013 #14

@Marc砾石

使用这个不是可以吗:

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

或者,同样的事情,在(Func<T, TResult>)Service<IOrderService>.Use

这将使返回变量更容易。

评论

2赞 Ruben Bartelink 5/16/2013
+1 @MarcGravell 我认为你的答案“可以做得更好”:P(操作 1 可以根据具有 null 返回值的 Func 来实现)。整个页面都是一团糟 - 如果我设想在这十年的任何时候使用 WCF,我会制定一个统一的页面并评论重复......
1赞 Andriy Buday 5/9/2013 #15
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();
            }
        }
    }
}

因此,它允许很好地编写返回语句:

return Service<IOrderService>.Use(orderService => 
{ 
    return orderService.PlaceOrder(request); 
}); 
-2赞 Uri Abramson 5/23/2013 #16

还可以使用 a 来扩展该方法。这样,您可以执行以下操作:DynamicProxyDispose()

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
0赞 hIpPy 8/16/2013 #17

我在这篇文章中提到了一些答案,并根据我的需要对其进行了定制。

我希望能够在使用WCF客户端之前对它做一些事情,所以方法。DoSomethingWithClient()

public interface IServiceClientFactory<T>
{
    T DoSomethingWithClient();
}
public partial class ServiceClient : IServiceClientFactory<ServiceClient>
{
    public ServiceClient DoSomethingWithClient()
    {
        var client = this;
        // do somthing here as set client credentials, etc.
        //client.ClientCredentials = ... ;
        return client;
    }
}

下面是帮助程序类:

public static class Service<TClient>
    where TClient : class, ICommunicationObject, IServiceClientFactory<TClient>, new()
{
    public static TReturn Use<TReturn>(Func<TClient, TReturn> codeBlock)
    {
        TClient client = default(TClient);
        bool success = false;
        try
        {
            client = new TClient().DoSomethingWithClient();
            TReturn result = codeBlock(client);
            client.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success && client != null)
            {
                client.Abort();
            }
        }
    }
}

我可以将其用作:

string data = Service<ServiceClient>.Use(x => x.GetData(7));

评论

0赞 Kiquenet 10/29/2014
使用绑定和结束的客户端构造函数是怎么回事?TClient(绑定、端部)
4赞 Johan Nyman 12/9/2013 #18

使用扩展方法:

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;
    }
}
0赞 Joe 1/15/2014 #19

我有自己的通道包装器,它实现了 Dispose,如下所示:

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

这似乎效果很好,并允许使用使用块。

0赞 Konstantin Spirin 12/23/2014 #20

以下帮助程序允许调用 non-void 方法。用法:void

var calculator = new WcfInvoker<CalculatorClient>(() => new CalculatorClient());
var sum = calculator.Invoke(c => c.Sum(42, 42));
calculator.Invoke(c => c.RebootComputer());

类本身是:

public class WcfInvoker<TService>
    where TService : ICommunicationObject
{
    readonly Func<TService> _clientFactory;

    public WcfInvoker(Func<TService> clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public T Invoke<T>(Func<TService, T> action)
    {
        var client = _clientFactory();
        try
        {
            var result = action(client);
            client.Close();
            return result;
        }
        catch
        {
            client.Abort();
            throw;
        }
    }

    public void Invoke(Action<TService> action)
    {
        Invoke<object>(client =>
        {
            action(client);
            return null;
        });
    }
}
1赞 InteXX 1/7/2015 #21

对于那些感兴趣的人,这里是已接受答案的 VB.NET 翻译(如下)。为了简洁起见,我对其进行了一些改进,结合了此线程中其他人的一些提示。

我承认它对于原始标签 (C#) 来说是题外话,但由于我无法找到这个优秀解决方案的 VB.NET 版本,我认为其他人也会这样做。Lambda 翻译可能有点棘手,所以我想省去别人的麻烦。

请注意,此特定实现提供了在运行时配置的功能。ServiceEndpoint


法典:

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

用法:

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
1赞 PSsam 1/14/2015 #22

我想从 Marc Gravell 的答案中添加 Service 的实现,以使用 ServiceClient 而不是 ChannelFactory。

public interface IServiceConnector<out TServiceInterface>
{
    void Connect(Action<TServiceInterface> clientUsage);
    TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage);
}

internal class ServiceConnector<TService, TServiceInterface> : IServiceConnector<TServiceInterface>
    where TServiceInterface : class where TService : ClientBase<TServiceInterface>, TServiceInterface, new()
{
    public TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage)
    {
        var result = default(TResult);
        Connect(channel =>
        {
            result = channelUsage(channel);
        });
        return result;
    }

    public void Connect(Action<TServiceInterface> clientUsage)
    {
        if (clientUsage == null)
        {
            throw new ArgumentNullException("clientUsage");
        }
        var isChanneldClosed = false;
        var client = new TService();
        try
        {
            clientUsage(client);
            client.Close();
            isChanneldClosed = true;
        }
        finally
        {
            if (!isChanneldClosed)
            {
                client.Abort();
            }
        }
    }
}
3赞 Lawrence 1/16/2015 #23

总结

使用此答案中描述的技术,可以使用以下语法在 using 块中使用 WCF 服务:

var channelFactory = new ChannelFactory<IMyService>("");

var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

当然,您可以进一步调整它,以实现特定于您的情况的更简洁的编程模型 - 但关键是我们可以创建一个重复通道的实现,该通道正确地实现了一次性模式。IMyService


到目前为止给出的所有答案都解决了 的 WCF 通道实现中的“错误”问题。似乎提供了最简洁的编程模型(允许您使用块来处置非托管资源)的答案是这个 - 其中代理被修改为通过无错误的实现来实现。这种方法的问题在于可维护性 - 我们必须为我们使用的代理重新实现此功能。在这个答案的变体中,我们将看到如何使用组合而不是继承来使这种技术通用。IDisposableusingIDisposable

第一次尝试

该实现似乎有多种实现,但为了论证起见,我们将使用当前接受的答案使用的改编。IDisposable

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    void DoWork();
}

public class ProxyDisposer : IDisposable
{
    private IClientChannel _clientChannel;


    public ProxyDisposer(IClientChannel clientChannel)
    {
        _clientChannel = clientChannel;
    }

    public void Dispose()
    {
        var success = false;
        try
        {
            _clientChannel.Close();
            success = true;
        }
        finally
        {
            if (!success)
                _clientChannel.Abort();
            _clientChannel = null;
        }
    }
}

public class ProxyWrapper : IMyService, IDisposable
{
    private IMyService _proxy;
    private IDisposable _proxyDisposer;

    public ProxyWrapper(IMyService proxy, IDisposable disposable)
    {
        _proxy = proxy;
        _proxyDisposer = disposable;
    }

    public void DoWork()
    {
        _proxy.DoWork();
    }

    public void Dispose()
    {
        _proxyDisposer.Dispose();
    }
}

有了上面的类,我们现在可以编写

public class ServiceHelper
{
    private readonly ChannelFactory<IMyService> _channelFactory;

    public ServiceHelper(ChannelFactory<IMyService> channelFactory )
    {
        _channelFactory = channelFactory;
    }

    public IMyService CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return new ProxyWrapper(channel, channelDisposer);
    }
}

这允许我们使用块来使用我们的服务:using

ServiceHelper serviceHelper = ...;
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

使它成为通用的

到目前为止,我们所做的只是重新制定托马斯的解决方案。阻止此代码成为通用代码的原因是,必须为我们想要的每个服务协定重新实现类。现在,我们将看一个类,它允许我们使用 IL 动态创建此类型:ProxyWrapper

public class ServiceHelper<T>
{
    private readonly ChannelFactory<T> _channelFactory;

    private static readonly Func<T, IDisposable, T> _channelCreator;

    static ServiceHelper()
    {
        /** 
         * Create a method that can be used generate the channel. 
         * This is effectively a compiled verion of new ProxyWrappper(channel, channelDisposer) for our proxy type
         * */
        var assemblyName = Guid.NewGuid().ToString();
        var an = new AssemblyName(assemblyName);
        var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName);

        var proxyType = CreateProxyType(moduleBuilder, typeof(T), typeof(IDisposable));

        var channelCreatorMethod = new DynamicMethod("ChannelFactory", typeof(T),
            new[] { typeof(T), typeof(IDisposable) });

        var ilGen = channelCreatorMethod.GetILGenerator();
        var proxyVariable = ilGen.DeclareLocal(typeof(T));
        var disposableVariable = ilGen.DeclareLocal(typeof(IDisposable));
        ilGen.Emit(OpCodes.Ldarg, proxyVariable);
        ilGen.Emit(OpCodes.Ldarg, disposableVariable);
        ilGen.Emit(OpCodes.Newobj, proxyType.GetConstructor(new[] { typeof(T), typeof(IDisposable) }));
        ilGen.Emit(OpCodes.Ret);

        _channelCreator =
            (Func<T, IDisposable, T>)channelCreatorMethod.CreateDelegate(typeof(Func<T, IDisposable, T>));

    }

    public ServiceHelper(ChannelFactory<T> channelFactory)
    {
        _channelFactory = channelFactory;
    }

    public T CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return _channelCreator(channel, channelDisposer);
    }

   /**
    * Creates a dynamic type analogous to ProxyWrapper, implementing T and IDisposable.
    * This method is actually more generic than this exact scenario.
    * */
    private static Type CreateProxyType(ModuleBuilder moduleBuilder, params Type[] interfacesToInjectAndImplement)
    {
        TypeBuilder tb = moduleBuilder.DefineType(Guid.NewGuid().ToString(),
            TypeAttributes.Public | TypeAttributes.Class);

        var typeFields = interfacesToInjectAndImplement.ToDictionary(tf => tf,
            tf => tb.DefineField("_" + tf.Name, tf, FieldAttributes.Private));

        #region Constructor

        var constructorBuilder = tb.DefineConstructor(
            MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName |
            MethodAttributes.RTSpecialName,
            CallingConventions.Standard,
            interfacesToInjectAndImplement);

        var il = constructorBuilder.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));

        for (var i = 1; i <= interfacesToInjectAndImplement.Length; i++)
        {
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg, i);
            il.Emit(OpCodes.Stfld, typeFields[interfacesToInjectAndImplement[i - 1]]);
        }
        il.Emit(OpCodes.Ret);

        #endregion

        #region Add Interface Implementations

        foreach (var type in interfacesToInjectAndImplement)
        {
            tb.AddInterfaceImplementation(type);
        }

        #endregion

        #region Implement Interfaces

        foreach (var type in interfacesToInjectAndImplement)
        {
            foreach (var method in type.GetMethods())
            {
                var methodBuilder = tb.DefineMethod(method.Name,
                    MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig |
                    MethodAttributes.Final | MethodAttributes.NewSlot,
                    method.ReturnType,
                    method.GetParameters().Select(p => p.ParameterType).ToArray());
                il = methodBuilder.GetILGenerator();

                if (method.ReturnType == typeof(void))
                {
                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);
                    il.Emit(OpCodes.Callvirt, method);
                    il.Emit(OpCodes.Ret);
                }
                else
                {
                    il.DeclareLocal(method.ReturnType);

                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);

                    var methodParameterInfos = method.GetParameters();
                    for (var i = 0; i < methodParameterInfos.Length; i++)
                        il.Emit(OpCodes.Ldarg, (i + 1));
                    il.Emit(OpCodes.Callvirt, method);

                    il.Emit(OpCodes.Stloc_0);
                    var defineLabel = il.DefineLabel();
                    il.Emit(OpCodes.Br_S, defineLabel);
                    il.MarkLabel(defineLabel);
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ret);
                }

                tb.DefineMethodOverride(methodBuilder, method);
            }
        }

        #endregion

        return tb.CreateType();
    }
}

使用新的帮助程序类,我们现在可以编写

var channelFactory = new ChannelFactory<IMyService>("");

var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

请注意,对于自动生成的客户端,也可以使用相同的技术(稍作修改),继承(而不是使用 ),或者如果要使用不同的实现来关闭通道。ClientBase<>ChannelFactory<>IDisposable

0赞 Murad Duraidi 9/3/2015 #24

覆盖客户端的 Dispose(),无需基于 ClientBase 生成代理类,也无需管理通道创建和缓存!(请注意,WcfClient 不是 ABSTRACT 类,而是基于 ClientBase)

// No need for a generated proxy class
//using (WcfClient<IOrderService> orderService = new WcfClient<IOrderService>())
//{
//    results = orderService.GetProxy().PlaceOrder(input);
//}

public class WcfClient<TService> : ClientBase<TService>, IDisposable
    where TService : class
{
    public WcfClient()
    {
    }

    public WcfClient(string endpointConfigurationName) :
        base(endpointConfigurationName)
    {
    }

    public WcfClient(string endpointConfigurationName, string remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
        base(binding, remoteAddress)
    {
    }

    protected virtual void OnDispose()
    {
        bool success = false;

        if ((base.Channel as IClientChannel) != null)
        {
            try
            {
                if ((base.Channel as IClientChannel).State != CommunicationState.Faulted)
                {
                    (base.Channel as IClientChannel).Close();
                    success = true;
                }
            }
            finally
            {
                if (!success)
                {
                    (base.Channel as IClientChannel).Abort();
                }
            }
        }
    }

    public TService GetProxy()
    {
        return this.Channel as TService;
    }

    public void Dispose()
    {
        OnDispose();
    }
}
2赞 Uriil 10/7/2015 #25

我喜欢这种关闭连接的方式:

var client = new ProxyClient();
try
{
    ...
    client.Close();
}
finally
{
    if(client.State != CommunicationState.Closed)
        client.Abort();
}
0赞 Aleksandr Albert 1/28/2016 #26

我这样做的方法是创建一个显式实现 IDisposable 的继承类。这对于使用 gui 添加服务引用 ( Add Service Reference ) 的人来说很有用。我只是在进行服务引用的项目中删除此类,并使用它而不是默认客户端:

using System;
using System.ServiceModel;
using MyApp.MyService; // The name you gave the service namespace

namespace MyApp.Helpers.Services
{
    public class MyServiceClientSafe : MyServiceClient, IDisposable
    {
        void IDisposable.Dispose()
        {
            if (State == CommunicationState.Faulted)
            {
                Abort();
            }
            else if (State != CommunicationState.Closed)
            {
                Close();
            }

            // Further error checks and disposal logic as desired..
        }
    }
}

注意:这只是 dispose 的简单实现,如果您愿意,可以实现更复杂的 dispose 逻辑。

然后,您可以将使用常规服务客户端进行的所有调用替换为安全客户端,如下所示:

using (MyServiceClientSafe client = new MyServiceClientSafe())
{
    var result = client.MyServiceMethod();
}

我喜欢这个解决方案,因为它不需要我访问接口定义,我可以按照我所期望的方式使用该语句,同时允许我的代码看起来或多或少相同。using

您仍然需要处理可能引发的异常,如此线程中的其他评论中指出的那样。