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


404

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

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

但是,正如此MSDN文章中所述,将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再次允许该块而没有掩盖故障状态异常的危险。

因此,使用这些解决方法是否还有其他陷阱?有没有人提出更好的建议?


42
最后一个(检查this.State)是一场比赛;在检查布尔值时它可能不会出错,但是在调用Close()时可能会出错。
布赖恩

15
你读状态;没错。在调用Close()之前,通道发生故障。Close()抛出。游戏结束。
布赖恩

4
时间流逝。这可能是很短的时间段,但是从技术上讲,在检查通道状态和要求其关闭之间的时间段内,通道的状态可能会发生变化。
埃里克·金

8
我会用Action<T>代替UseServiceDelegate<T>。次要。
2013年

2
我真的不喜欢这个静态助手,Service<T>因为它会使单元测试变得复杂(就像大多数静态东西一样)。我希望它是非静态的,因此可以将其注入使用它的类中。
Fabio Marreco

Answers:


137

实际上,尽管我写了博客(请参阅Luke的答案),但我认为比我的IDisposable包装更好。典型代码:

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

(按评论编辑)

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

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

2
@MarcGravell我可以在哪里注入该客户端?我假设ChannelFactory创建了客户端,并且在Service类内部更新了工厂对象,这意味着应该对代码进行一些重构以允许自定义工厂。这是正确的,还是我在这里遗漏了一些明显的东西?
安图

16
您可以轻松地修改包装器,因此您不需要为结果捕获变量。像这样: public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
克里斯

3
也许有用 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
PreguntonCojoneroCabrón

如何使用这种方式添加凭据?
Hippasus

2
我认为,最正确的解决方案是: 1)在没有竞争条件的情况下执行Close / Abort模式2)处理服务操作引发异常时的情况3)处理Close和Abort方法都引发异常时的情况异步异常,例如ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet

88

在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
在非托管资源中使用“最后尝试”(或语法糖-“ using(){}”)语句是否有风险?例如,如果“关闭”选项失败,则不会捕获该异常,最后可能无法运行。同样,如果finally语句中存在异常,则它可以掩盖其他异常。我认为这就是为什么首选Try-Catch的原因。
Zack Jannsen

扎克,不清楚你的对象;我想念什么?如果Close方法引发异常,则将在引发异常之前执行finally块。对?
Patrick Szalapski 2014年

1
@jmoreno,我撤消了您的编辑。如果您会注意到,该方法中根本没有catch块。这个想法是,任何发生的异常(甚至是最后的异常)都应该抛出,而不是默默地捕获。
马特·戴维斯

5
@MattDavis为什么根本不需要success标志?为什么不try { Close(); } catch { Abort(); throw; }呢?
康斯坦丁·斯皮林2014年

尝试一下/捕捉一下Close(); success = true;呢?如果我可以在finally块中成功中止该异常,则不希望引发异常。我只想在Abort()失败的情况下引发异常。这样,try / catch将隐藏潜在的竞争条件异常,并仍然允许您在finally块中中止连接。
goku_da_master

32

我编写了一个更高阶的函数以使其正常工作。我们已经在多个项目中使用了它,并且看起来效果很好。这就是从一开始就应该做的事情,没有“使用”范式之类的事情。

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属性为null。ChannelFactory的端点必须具有指定的有效地址。什么是GetCachedFactory方法?
马歇尔

28

这是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

考虑到有这么多的问题,涉及到使用的语句(加热?)的内部讨论线程在这个问题上,我不会浪费我的时间,试图成为一个代码牛仔,找到一个更清洁的方式。我将介绍它,并以冗长(至今仍受信任)的方式为我的服务器应用程序实现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
}

诚然,这是一些平凡的代码。我目前更喜欢这个答案,并且在该代码中看不到任何可能导致问题的“ hacks”。


1
样本中的代码是否仍然引起问题?我尝试运行UsingUsing项目(VS2013),但该行"Hope this code wasn't important, because it might not happen."仍在执行...
janv8000 2014年

14

我终于找到了一些可靠的步骤,可以找到一种解决该问题的干净方法。

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

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

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


@Shimmy Status Beta。日期:星期六2009年7月11日米歇尔·布斯塔曼特。死项目?
Kiquenet

11

根据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<T>如果需要,您可以将其与子代而不是ChannelFactory一起使用。

如果开发人员想手动处理代理/通道,则可以使用扩展方法。


如果我使用的是PoolingDuplex并且在通话后不关闭连接,则使用此选项很有意义,因此我的客户端服务可能会持续几天甚至可以处理服务器回调。据我了解,这里讨论的解决方案对于每个会话一个呼叫有意义吗?
sll 2012年

@sll-用于在呼叫返回后立即关闭连接(每个会话一个呼叫)。
TrueWill 2012年

@cacho DisposeSafely私有化当然是一种选择,并且可以避免造成混乱。在某些情况下,有些人可能想直接调用它,但我不能一口气拿出来。
TrueWill

@truewill仅用于文档说明,还很重要的一点是,此方法是线程安全的,对吗?
Cacho Santa

1
我认为,最正确的解决方案是: 1)在没有竞争条件的情况下执行Close / Abort模式2)处理服务操作引发异常时的情况3)处理Close和Abort方法都引发异常时的情况异步异常,例如ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet

8

@马克·格雷韦尔

可以使用这个吗?

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
+1 @MarcGravell我想您的回答也“可以做得更好”:P(而且可以用一个具有空返回值的Func来实现动作)。这整个页面是一个烂摊子-我会去制定有关的DUP统一的一个评论,如果我设想使用WCF任何时候这十年...
鲁文Bartelink

7

这是什么?

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

接受的答案引用了不再存在的该网站。为了节省您的麻烦,我在这里列出了最相关的部分。此外,我对其进行了少许修改,以包括异常重试处理来处理那些令人讨厌的网络超时。

简单的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。我不会从此答案中收集“要点”,但是如果您同意该实现,则希望您对其进行投票,或者对其进行编辑以使其更佳。


我不确定我是否同意你对这个答案的描述。这是CW版本,其中添加了对异常处理想法
约翰·桑德斯

@JohnSaunders-True(我的异常处理概念)。让我知道我遗漏的任何异常或处理不当的异常。
goodguys_activate 2012年

什么是成功变量?它需要添加到源代码中:if(成功)返回;??
Kiquenet

如果第一次调用引发而第二次调用成功,mostRecentEx不会为空,因此您抛出的异常无论如何都会重试5次。还是我错过了什么?如果在第二,第三,第四或第五次尝试均成功,我看不到您清除mostRecentEx的位置。也看不到回报或成功。我应该在这里丢失一些东西,但是如果没有异常抛出,此代码将不会总是运行5次?
Bart Calixto 2014年

@Bart-我success == false在最后的if语句中添加了
goodguys_activate 2014年

7

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

它使用.NET 4(特别是:convariance,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
为什么用UseServiceDelegate<T>代替Action<T>
Mike Mayer 2013年

1
我可以认为原始作者这样做的唯一原因是拥有一个开发人员会知道的强类型委托,该委托属于调用服务。但是,据我所知,它也Action<T>一样有效。
Jesse C. Slicer 2013年

5

这样的包装器可以工作:

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的讨论……导致W / WCF内存泄漏。
goodguys_activate 2011年

我不确定这是否导致内存泄漏,但是问题是这样的。当您调用DisposeIChannel时,如果通道处于故障状态,则可能引发异常,这是一个问题,因为Microsoft指定Dispose永远不要引发。因此,上面的代码在Close引发异常时处理情况。如果Abort抛出,则可能是严重错误。去年12月,我写了一篇有关它的博客文章:blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
Tomas Jansson,

4

我使用了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动态代理


4

使用扩展方法:

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

4

如果您不需要IoC或正在使用自动生成的客户端(服务参考),则可以简单地使用包装器来管理关闭操作,并让GC在安全状态下(不引发任何异常)使用clientbase。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在autodisconect中使用:

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

    Ws.Open();

    Ws.Test();
}

3

摘要

使用此答案中描述的技术,可以使用以下语法在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通道实现中解决“错误”的问题IDisposable。似乎提供了最简洁的编程模型(允许您使用该using块将其放置在非托管资源上)的答案是这一点 -修改了代理以实现无错误的IDisposable实现。这种方法的问题是可维护性-我们必须为使用的代理重新实现此功能。在此答案的变体中,我们将看到如何使用合成而不是继承来使该技术通用。

第一次尝试

该实现似乎有各种各样的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();
}

使这个通用

到目前为止,我们所做的就是重新制定Tomas的解决方案。阻止此代码通用的原因是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<>(而不是使用ChannelFactory<>)使用相同的技术(稍作修改),或者如果您想使用其他实现IDisposable关闭频道,也可以使用该技术。


2

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

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

1

我编写了一个简单的基类来处理此问题。它以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);
    }
}

VS2013-.net 4.5.1是否有任何更新?重试的任何选项,例如stackoverflow.com/a/9370880/206730?–
Kiquenet 2014年

@Kiquenet我不再使用WCF。如果您向我发送请求请求,我可以将其合并并更新程序包。
UfukHacıoğulları14年

1
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语句:

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

1

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

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

1

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

我们的系统架构通常使用Unity IoC框架来创建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方法
CaffGeek

@Chad-我遵循的是Microsoft常见的Finalize / Dispose设计模式:msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx 确实我没有使用该变量,因为我没有在正常处理和完成之间无需进行任何其他清除。可以重写它,使其具有Finalize调用Dispose()并将代码从Dispose(bool)移到Dispose()。
CodingWithSpike

终结器增加了开销,并且不确定。我会尽可能避免它们。您可以使用Unity的自动工厂来注入委托并将其放入使用块中,或者(更好)将创建/调用/处置服务行为隐藏在注入接口上的方法后面。每次对依赖项的调用都会创建代理,对其进行调用并进行处理。
TrueWill 2012年

0

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

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

使用绑定和endpoing的Client构造函数如何?TClient(绑定,终止)
Kiquenet 2014年

0

我为实现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块。


0

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

0

覆盖客户端的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();
    }
}

0

我这样做的方法是创建一个显式实现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按我期望的方式使用该语句,同时使我的代码大致相同。

您仍然需要处理该线程中其他注释所指出的可能引发的异常。


-2

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

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.