关闭C# WCF通道并使用函数Func<T>

4
这是要翻译的内容:

重点在这里,我有一个 WCF 服务,它现在正在工作。所以我开始在客户端上工作。当应用程序运行时,出现了一个异常:超时。于是我开始阅读,有许多关于如何保持连接活动的示例,但是我发现最好的方法是创建通道,使用它,并且释放它。老实说,我很喜欢这种方法。现在,关于关闭通道的最佳方式的阅读,有两个链接可能对任何需要它们的人有用:

1. 正确清理客户端

2. 使用 Func

在第一个链接中,这是示例:

    IIdentityService _identitySvc;
...
if (_identitySvc != null)
 {
     ((IClientChannel)_identitySvc).Close();
     ((IDisposable)_identitySvc).Dispose();
     _identitySvc = null;
 }

因此,如果通道不为null,则关闭、清理并分配null。但我有一个小问题。在这个例子中,通道有一个.Close()方法,但是,在我的情况下,智能提示没有显示Close()方法。它只存在于工厂对象中。所以我相信我必须自己编写它。但是,在具有契约的接口或实现它的类中应该怎么写呢?而且,这个方法应该做些什么?

现在,下一个链接,这是我以前没有尝试过的东西。Func<T>。阅读目标后,它非常有趣。它创建一个函数,使用lambda创建通道,使用它,关闭它并释放它。这个例子将该函数实现为Using()语句。这真的很好,是一个极好的改进。但是,老实说,我需要一点帮助,我无法理解这个函数,所以,来自专家的一点解释将非常有用。这是这个函数:

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

是的,我认为这真的非常好,我想了解如何在我拥有的项目中使用它。

而且,像往常一样,我希望这对很多人有所帮助。


实际上,我的问题并不完全是关于通道的,而是关于泛型。我还停留在这个问题上。欢迎任何帮助解释“UseService”函数的说明! - BlackCath
3个回答

3

UseService方法接受一个委托,该委托使用通道发送请求。委托具有参数和返回值。您可以将对WCF服务的调用放在委托中。

在UseService中,它创建通道并将通道传递给应由您提供的委托。完成调用后,它关闭通道。


你好!非常感谢你的回答,现在问题已经开始变得清晰了。然而,我仍然卡在这个语句上:`var chanFactory = GetCachedFactory();`我找不到这个方法,所以必须自己编写,但是,...怎么编写呢?我的意思是,真的怎么编写呢?感谢你的关注。 - BlackCath

2
代理对象不仅实现了您的契约,还实现了IClientChannel,它允许控制代理生命周期。
第一个示例中的代码不可靠 - 如果通道已经损坏(例如,在基于会话的交互中服务已停止),它将造成资源泄漏。正如您在第二个版本中看到的那样,在出现错误的情况下,它调用代理上的Abort方法,这仍然可以清理客户端端。
您还可以使用扩展方法来完成此操作:
 enum OnError
 {
     Throw,
     DontThrow
 }

 static class ProxyExtensions
 {
     public static void CleanUp(this IClientChannel proxy, OnError errorBehavior)
     {
         try
         {
             proxy.Close();
         }
         catch
         {
             proxy.Abort();

             if (errorBehavior == OnError.Throw)
             {
                 throw;
             }
         }
     }
 }

然而,这种用法有点繁琐。
 ((IClientChannel)proxy).CleanUp(OnError.DontThrow);

但是,如果您创建自己的代理接口并扩展您的合同和IClientChannel,则可以使其更加优雅。
interface IPingProxy : IPing, IClientChannel
{

}

1
为了回答Jason答案中留下的评论问题,GetCachedFactory的一个简单示例可能如下所示。该示例通过查找配置文件中“Contract”属性等于工厂要创建的服务的ConfigurationName的端点来查找要创建的端点。
ChannelFactory<T> GetCachedFactory<T>()
{
    var endPointName = EndPointNameLookUp<T>();
    return new ChannelFactory<T>(endPointName);
}

// Determines the name of the endpoint the factory will create by finding the endpoint in the config file which is the same as the type of the service the factory is to create
string EndPointNameLookUp<T>()
{
    var contractName = LookUpContractName<T>();
    foreach (ChannelEndpointElement serviceElement in ConfigFileEndPoints)
    {
        if (serviceElement.Contract == contractName) return serviceElement.Name;
    }
    return string.Empty;
}

// Retrieves the list of endpoints in the config file
ChannelEndpointElementCollection ConfigFileEndPoints
{
    get
    {
        return ServiceModelSectionGroup.GetSectionGroup(
            ConfigurationManager.OpenExeConfiguration(
                ConfigurationUserLevel.None)).Client.Endpoints;
    }
}

// Retrieves the ConfigurationName of the service being created by the factory
string LookUpContractName<T>()
{
    var attributeNamedArguments = typeof (T).GetCustomAttributesData()
        .Select(x => x.NamedArguments.SingleOrDefault(ConfigurationNameQuery));

    var contractName = attributeNamedArguments.Single(ConfigurationNameQuery).TypedValue.Value.ToString();
    return contractName;
}

Func<CustomAttributeNamedArgument, bool> ConfigurationNameQuery
{
    get { return x => x.MemberInfo != null && x.MemberInfo.Name == "ConfigurationName"; }
}

更好的解决方案是让一个IoC容器来管理客户端的创建。例如,使用autofac,它会像下面这样。首先,您需要像这样注册服务:
var builder = new ContainerBuilder();

builder.Register(c => new ChannelFactory<ICalculator>("WSHttpBinding_ICalculator"))
  .SingleInstance();
builder.Register(c => c.Resolve<ChannelFactory<ICalculator>>().CreateChannel())
  .UseWcfSafeRelease();

container = builder.Build();

"WSHttpBinding_ICalculator" 是配置文件中端点的名称。稍后您可以这样使用服务:

using (var lifetime = container.BeginLifetimeScope())
{
    var calc = lifetime.Resolve<IContentService>();
    var sum = calc.Add(a, b);
    Console.WriteLine(sum);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接