【问题标题】:What is the best workaround for the WCF client `using` block issue?WCF 客户端“使用”块问题的最佳解决方法是什么?
【发布时间】:2010-10-09 02:25:55
【问题描述】:

我喜欢在 using 块中实例化我的 WCF 服务客户端,因为这几乎是使用实现 IDisposable 的资源的标准方式:

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

但是,如 this MSDN article 中所述,将 WCF 客户端包装在 using 块中可能会掩盖导致客户端处于故障状态(如超时或通信问题)的任何错误。长话短说,当调用Dispose() 时,客户端的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 块,而不会掩盖错误状态异常。

那么,在使用这些变通方法时,我还需要注意哪些其他问题?有没有人想出更好的办法?

【问题讨论】:

  • 最后一个(检查 this.State)是一场比赛;检查布尔值时可能不会出错,但调用 Close() 时可能会出错。
  • 你读过状态;它没有错。在调用 Close() 之前,通道发生故障。 Close() 抛出。游戏结束。
  • 时间流逝。这可能是很短的一段时间,但从技术上讲,在检查通道状态和要求关闭通道之间的时间段内,通道的状态可能会发生变化。
  • 我会使用Action&lt;T&gt; 而不是UseServiceDelegate&lt;T&gt;。次要的。
  • 我真的不喜欢这个静态助手Service&lt;T&gt;,因为它使单元测试变得复杂(就像大多数静态事情一样)。我希望它是非静态的,以便可以将其注入到使用它的类中。

标签: c# vb.net wcf using wcf-client


【解决方案1】:

实际上,虽然我blogged(见Luke's answer),但我认为this 比我的IDisposable 包装器要好。典型代码:

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

(按 cmets 编辑)

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

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

【讨论】:

  • @MarcGravell 我可以在哪里注入该客户端?我假设 ChannelFactory 创建客户端,并且工厂对象是在 Service 类中新建的,这意味着应该对代码进行一些重构以允许自定义工厂。这是正确的,还是我在这里遗漏了一些明显的东西?
  • 您可以轻松地修改包装器,因此您不需要捕获变量来获取结果。像这样的东西:public static TResult Use&lt;TResult&gt;(Func&lt;T, TResult&gt; codeBlock) { ... }
  • 也许有用 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
  • 如何使用这种方式添加凭证?
  • 在我看来,最正确的解决方案是:1)在没有竞争条件的情况下执行关闭/中止模式 2)处理服务操作抛出异常时的情况 3)处理Close 和 Abort 方法都抛出异常的情况 4) 处理 ThreadAbortException 等异步异常 https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
【解决方案2】:

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

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

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

【讨论】:

  • 对非托管资源使用“Try-Finally”(或语法糖——“using(){}”)语句是否有风险?例如,如果“关闭”选项失败,则不会捕获异常,最终可能无法运行。此外,如果 finally 语句中有异常,它可以掩盖其他异常。我认为这就是为什么首选 Try-Catch 的原因。
  • 扎克,不清楚你的对象;我错过了什么?如果 Close 方法抛出异常,finally 块将在抛出异常之前执行。对吗?
  • @jmoreno,我撤消了您的编辑。如果您注意到,该方法中根本没有 catch 块。这个想法是应该抛出任何发生的异常(即使在finally中),而不是静默捕获。
  • @MattDavis 为什么你需要success 标志?为什么不try { Close(); } catch { Abort(); throw; }
  • Close(); success = true; 周围放置一个try/catch 怎么样?如果我可以在 finally 块中成功中止它,我不希望抛出异常。如果 Abort() 在这种情况下失败,我只想抛出一个异常。这样,try/catch 将隐藏潜在的竞争条件异常,并且仍然允许您在 finally 块中 abort() 连接。
【解决方案3】:

我写了一个higher order function 让它正常工作。我们已经在几个项目中使用了它,它似乎工作得很好。这就是从一开始就应该做的事情,没有“使用”范式等等。

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”方法助手,我们可以按照我们想要的方式配置通道工厂,等等。我们也不必使用生成的代理——任何接口都可以.

【讨论】:

  • 我遇到异常:ChannelFactory.Endpoint 上的 Address 属性为空。 ChannelFactory 的端点必须指定一个有效的地址。什么是GetCachedFactory 方法?
【解决方案4】:

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

更多详情请见:Expected Exceptions

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 上问这个问题,以至于微软甚至创建了一个专门的示例来演示如何处理异常:

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

下载示例: C#VB

考虑到involving the using statement(heated?) Internal discussionsthreads 在这个问题上存在很多问题,我不会浪费时间尝试成为代码牛仔并找到更清洁的方法。我将把它吸干,并为我的服务器应用程序以这种冗长(但受信任)的方式实现 WCF 客户端。

可选的附加故障捕获

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

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

诚然,这是一段平凡的代码。我目前更喜欢this answer,并且在该代码中看不到任何可能导致问题的“黑客”。

【讨论】:

  • 示例中的代码是否仍然会导致问题?我尝试运行 UsingUsing 项目(VS2013),但 "Hope this code wasn't important, because it might not happen." 的行仍在执行...
【解决方案5】:

我终于找到了一些可靠的步骤来解决这个问题。

此自定义工具扩展了 WCFProxyGenerator 以提供异常处理代理。它生成一个名为ExceptionHandlingProxy&lt;T&gt; 的附加代理,它继承ExceptionHandlingProxyBase&lt;T&gt; - 后者实现了代理功能的核心。结果是您可以选择使用继承ClientBase&lt;T&gt;ExceptionHandlingProxy&lt;T&gt; 的默认代理,它封装了管理通道工厂和通道的生命周期。 ExceptionHandlingProxy 尊重您在“添加服务引用”对话框中关于异步方法和集合类型的选择。

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

您可以安全地再次使用Using 语句,如果通道因任何请求(TimeoutException 或 CommunicationException)发生故障,Wrapper 将重新初始化故障通道并重试查询。如果失败,那么它将调用Abort() 命令并处理代理并重新抛出异常。如果服务抛出 FaultException 代码,它将停止执行,并且代理将按预期安全地中止并抛出正确的异常。

【讨论】:

  • @Shimmy 状态测试版。 日期:2009 年 7 月 11 日星期六,作者 Michele Bustamante。死项目?
【解决方案6】:

根据 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方法时不必返回伪值,可以多次调用服务(并返回多个值)而不必使用元组。

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

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

【讨论】:

  • 如果我使用 PoolingDuplex 并且在通话后不关闭连接,那么我的客户端服务可能会存活几天并处理服务器回调,那么使用它是有意义的。据我了解,这里讨论的解决方案对于每个会话一次调用有意义?
  • @sll - 这是为了在调用返回后立即关闭连接(每个会话一次调用)。
  • @cacho 将DisposeSafely 设为私有当然是一种选择,并且可以避免混淆。可能会有人想直接调用它的用例,但我不能随便想出一个。
  • @truewill 仅用于文档,还需要提到此方法是线程安全的,对吗?
  • 在我看来,最正确的解决方案是:1)在没有竞争条件的情况下执行关闭/中止模式 2)处理服务操作抛出异常时的情况 3)处理Close和Abort方法都抛出异常的情况 4) 处理ThreadAbortException等异步异常 https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
【解决方案7】:

@Marc Gravell

用这个不行吗:

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&lt;T, TResult&gt;)Service&lt;IOrderService&gt;.Use 的情况相同

这些将使返回变量更容易。

【讨论】:

  • +1 @MarcGravell 我认为您的回答“还可以做得更好”:P(并且可以根据返回 null 的 Func 来实施操作)。整个页面一团糟——如果我设想在这十年的任何时候使用 WCF,我会制定一个统一的页面并评论重复...
【解决方案8】:

这是什么?

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

接受的答案引用this website that is no longer around。为了省去你的麻烦,我在这里包括了最相关的部分。此外,我稍微修改了它以包含 exception retry handling 来处理那些讨厌的网络超时。

简单的 WCF 客户端用法

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

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

ServiceDelegate.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:我已将此帖子设为社区 wiki。我不会从这个答案中收集“积分”,但如果你同意实施,我希望你支持它,或者编辑它以使其更好。

【讨论】:

  • 我不确定我是否同意您对这个答案的描述。这是添加了your idea of exception handling的CW版本。
  • @JohnSaunders - 是的(我的异常处理概念)。让我知道我遗漏或处理不当的任何异常。
  • 成功变量是什么?它需要在源代码中添加: if (success) return; ??
  • 如果第一次调用抛出并且第二次成功 mostRecentEx 不会为空,那么您将抛出一个异常,但 5 次重试失败。还是我错过了什么?如果第 2 次、第 3 次、第 4 次或第 5 次尝试成功,我看不到您清除 mostRecentEx 的位置。也看不到成功的回报。我应该在这里遗漏一些东西,但是如果没有抛出异常,这段代码不会总是运行 5 次?
  • @Bart - 我在最后的 if 语句中添加了success == false
【解决方案9】:

以下是来自the question 的增强版源,并扩展为缓存多个通道工厂,并尝试通过合约名称在配置文件中查找端点。

它使用 .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.");
    }
}

【讨论】:

  • 为什么使用UseServiceDelegate&lt;T&gt; 而不是Action&lt;T&gt;
  • 我认为原作者这样做的唯一原因是有一个强类型的委托,开发人员会知道它属于调用服务。但是,据我所知,Action&lt;T&gt; 也同样有效。
【解决方案10】:

这样的包装器可以工作:

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.

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

【讨论】:

  • 我记得讨论过在某些条件下未调用 Dispose...导致 WCF 内存泄漏。
  • 我不确定它是否会导致内存泄漏,但问题是这样的。当您在 IChannel 上调用 Dispose 时,如果通道处于故障状态,它可能会抛出异常,这是一个问题,因为 Microsoft 指定永远不应抛出 Dispose。所以上面的代码所做的就是处理Close 抛出异常的情况。如果Abort 抛出它可能是严重错误。去年 12 月我写了一篇关于它的博客文章:blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
【解决方案11】:

我使用 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 Dynamic Proxy

【讨论】:

    【解决方案12】:

    使用扩展方法:

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

    【讨论】:

      【解决方案13】:

      如果您不需要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;
              }
          }
      }
      

      然后在访问服务器时,创建客户端并在自动断开连接中使用using

      var Ws = new ServiceClient("netTcpEndPointName");
      using (new AutoCloseWcf(Ws)) {
      
          Ws.Open();
      
          Ws.Test();
      }
      

      【讨论】:

        【解决方案14】:

        总结

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

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

        您当然可以进一步调整它以实现针对您的情况的更简洁的编程模型 - 但关键是我们可以创建一个 IMyService 的实现来表示正确实现一次性模式的通道。


        详情

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

        第一次尝试

        IDisposable 实现似乎有多种实现方式,但为了论证,我们将使用currently accepted answer 使用的一种改编。

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

        使这个通用

        到目前为止,我们所做的只是重新格式化Tomas' solution。阻止此代码通用的原因是 ProxyWrapper 类必须为我们想要的每个服务合同重新实现。我们现在将看一个允许我们使用 IL 动态创建此类型的类:

        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&lt;&gt;(而不是使用 ChannelFactory&lt;&gt;)的自动生成客户端,或者如果您想使用 IDisposable 的不同实现,您也可以使用相同的技术(稍作修改)关闭您的频道。

        【讨论】:

          【解决方案15】:

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

          var client = new ProxyClient();
          try
          {
              ...
              client.Close();
          }
          finally
          {
              if(client.State != CommunicationState.Closed)
                  client.Abort();
          }
          

          【讨论】:

            【解决方案16】:

            我已经写了a simple base class 来处理这个问题。它以NuGet package 的形式提供,而且非常易于使用。

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

            【讨论】:

            • VS2013-.net 4.5.1 的任何更新?任何重试选项,例如stackoverflow.com/a/9370880/206730? ——
            • @Kiquenet 我不再从事 WCF 工作。如果您向我发送拉取请求,我可以合并它并更新包。
            【解决方案17】:
            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); 
            }); 
            

            【讨论】:

              【解决方案18】:

              我想从 Marc Gravell's answer 添加 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();
                          }
                      }
                  }
              }
              

              【讨论】:

                【解决方案19】:

                对于那些感兴趣的人,这里是接受答案的 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
                

                【讨论】:

                  【解决方案20】:

                  我们的系统架构经常使用UnityIoC 框架来创建ClientBase 的实例,因此无法确保其他开发人员甚至使用using{} 块。为了使其尽可能简单,我创建了这个扩展 ClientBase 的自定义类,并在处置时关闭通道,或者在最终确定时关闭通道,以防有人没有明确处置 Unity 创建的实例。

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

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

                  【讨论】:

                  • 你永远不会在你的 Dispose 方法中使用参数 disposing
                  • @Chad - 我遵循 Microsoft 常见的 Finalize/Dispose 设计模式:msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx 不过我确实没有使用该变量,因为我不需要在正常处置和最终确定。它可以重写为只调用 Dispose() 并将代码从 Dispose(bool) 移动到 Dispose()。
                  • 终结器会增加开销,并且不是确定性的。我尽可能避免使用它们。您可以使用 Unity 的自动工厂来注入委托并将其放入 using 块中,或者(更好地)将创建/调用/处置服务行为隐藏在注入接口上的方法后面。对依赖项的每次调用都会创建代理、调用它并释放它。
                  【解决方案21】:

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

                  我希望能够在使用 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));
                  

                  【讨论】:

                  • 使用绑定和端点的客户端构造函数怎么样? TClient(绑定,端点)
                  【解决方案22】:

                  我有自己的通道包装器,它实现 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;
                          }
                  }
                  

                  这似乎运作良好,并允许使用 using 块。

                  【讨论】:

                    【解决方案23】:

                    以下帮助程序允许调用 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;
                            });
                        }
                    }
                    

                    【讨论】:

                      【解决方案24】:

                      重写客户端的Dispose(),无需基于ClientBase生成代理类,也无需manage channel creation and caching! (注意,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();
                          }
                      }
                      

                      【讨论】:

                        【解决方案25】:

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

                        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 语句,同时让我的代码看起来或多或少相同。

                        您仍然需要处理可能引发的异常,正如该线程中其他 cmets 所指出的那样。

                        【讨论】:

                          【解决方案26】:

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

                          using (var wrapperdProxy = new Proxy<yourProxy>())
                          {
                             // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
                          }
                          

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2011-11-10
                            • 1970-01-01
                            • 2018-12-19
                            • 1970-01-01
                            相关资源
                            最近更新 更多