【发布时间】:2017-03-23 17:52:56
【问题描述】:
我遇到了 WCF 服务方法调用的问题,该方法调用冻结并且不返回响应如果在同一进程上完成连接...
这是完整的代码(非常简化,只显示需要的内容):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
namespace TestProject
{
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IBaseServiceCallback))]
public interface IBaseService
{
[OperationContract]
bool IsServiceInitiated();
}
public interface IBaseServiceCallback
{
[OperationContract]
bool IsClientInitiated();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, IncludeExceptionDetailInFaults = true, AutomaticSessionShutdown = false, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class BaseService : IBaseService
{
public bool IsServiceInitiated()
{
return true;
}
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, IncludeExceptionDetailInFaults = true, AutomaticSessionShutdown = false, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class BaseServiceCallback : IBaseServiceCallback
{
public bool IsClientInitiated()
{
return true;
}
}
public class CustomServer
{
private BaseService service;
private ServiceHost host;
public bool IsStarted
{
get { return host != null && host.State == CommunicationState.Opened; }
}
public CustomServer()
{
service = new BaseService();
host = new ServiceHost(service, new Uri[] { new Uri("net.tcp://localhost:7780") });
Type interfaceType = typeof(IBaseService);
// Create service end point
ServiceEndpoint endpointPipe = host.AddServiceEndpoint(interfaceType, new NetTcpBinding(), "CustomService");
// Define TCP binding
NetTcpBinding bindingPipe = (NetTcpBinding)endpointPipe.Binding;
bindingPipe.MaxReceivedMessageSize = 5000000;
bindingPipe.MaxBufferPoolSize = 5000000;
bindingPipe.MaxBufferSize = 5000000;
bindingPipe.ReaderQuotas.MaxDepth = 2048;
bindingPipe.Security.Mode = SecurityMode.None;
bindingPipe.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;
bindingPipe.Security.Message.ClientCredentialType = MessageCredentialType.None;
// Increase MaxItemsInObjectGraph for all operations behaviors
foreach (OperationDescription op in endpointPipe.Contract.Operations)
{
var dataContractBehavior = op.Behaviors.Find<DataContractSerializerOperationBehavior>();
if (dataContractBehavior != null)
{
dataContractBehavior.MaxItemsInObjectGraph = int.MaxValue;
}
}
// In order to publish the service contract, it is important to publish the metadata
ServiceMetadataBehavior smb = host.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (smb == null)
{
smb = new ServiceMetadataBehavior();
}
smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
host.Description.Behaviors.Add(smb);
// Add MEX endpoint
host.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexTcpBinding(), "net.tcp://localhost:7780/IDMmex");
}
public void Start()
{
// Open for listening
host.Open();
}
public void Stop()
{
// Stop listening
host.Close();
}
}
public class CustomClient
{
public IBaseService ServiceProxy { get; private set; }
private BaseServiceCallback callback;
public CustomClient()
{
callback = new BaseServiceCallback();
}
public void Connect()
{
string serviceUrl = "net.tcp://localhost:7780/CustomService";
// Create a channel in order to find the exact call back type.
DuplexChannelFactory<IBaseService> sampleChannel = new DuplexChannelFactory<IBaseService>(callback, new NetTcpBinding(), new EndpointAddress(serviceUrl));
Type duplexChannelFactory = typeof(DuplexChannelFactory<>).MakeGenericType(new Type[] { typeof(IBaseService) });
object pipeFactory = Activator.CreateInstance(duplexChannelFactory, new object[] { callback, new NetTcpBinding(), new EndpointAddress(serviceUrl) });
// Get the service end point
ServiceEndpoint endpoint = (ServiceEndpoint)duplexChannelFactory.GetProperty("Endpoint").GetValue(pipeFactory, null);
// Configure TCP binding
NetTcpBinding tcpBinding = (NetTcpBinding)endpoint.Binding;
tcpBinding.MaxReceivedMessageSize = 5000000;
tcpBinding.MaxBufferPoolSize = 5000000;
tcpBinding.MaxBufferSize = 5000000;
tcpBinding.ReaderQuotas.MaxDepth = 2048;
tcpBinding.Security.Mode = SecurityMode.None;
tcpBinding.Security.Message.ClientCredentialType = MessageCredentialType.None;
tcpBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;
tcpBinding.SendTimeout = TimeSpan.MaxValue;
tcpBinding.ReceiveTimeout = TimeSpan.MaxValue;
tcpBinding.OpenTimeout = TimeSpan.MaxValue;
tcpBinding.CloseTimeout = TimeSpan.MaxValue;
// Increase MaxItemsInObjectGraph for all operations behaviors
foreach (OperationDescription op in endpoint.Contract.Operations)
{
var dataContractBehavior = op.Behaviors.Find<DataContractSerializerOperationBehavior>();
if (dataContractBehavior != null)
{
dataContractBehavior.MaxItemsInObjectGraph = int.MaxValue;
}
}
// Create the channel to retrieve the pipe proxy object
MethodInfo method = duplexChannelFactory.GetMethod("CreateChannel", new Type[0]);
object pipeProxyObject = method.Invoke(pipeFactory, new object[] { });
// Set the service proxy with the retrieved pipe proxy object
ServiceProxy = (IBaseService)pipeProxyObject;
}
}
}
然后,我有一个 Windows 窗体应用程序,它启动一个 CustomServer、一个 CustomClient 并尝试进行通信:
CustomClient customClient = new CustomClient();
CustomServer customServer = new CustomServer();
customServer.Start();
customClient.Connect();
if (customClient.ServiceProxy.IsServiceInitiated()) // FREEZE HERE !!
{
MessageBox.Show("Server initiated");
}
连接完成,但调用“IsServiceInitiated”方法时,应用程序冻结。
但是,如果创建一个专用于启动服务器的应用程序,以及另一个专用于连接到服务器的客户端的应用程序,则该方法不会冻结并返回 true。
真的需要任何帮助。
非常感谢。
编辑: 好的,我在控制台中添加了消息检查器和调度检查器,并输出了 BeforeSendRequest 和 AfterReceiveRequest 的结果。结果如下:
Before send request: Action = http://tempuri.org/IBaseService/IsServiceInitiated Reply =
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">http://tempuri.org/IBaseService/IsServiceInitiated</a:Action>
<a:MessageID>urn:uuid:d14b0af4-81c3-46c7-9b38-83a8fb092028</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<VsDebuggerCausalityData xmlns="http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink">uIDPoy1+4Je4rwBHkZsB8NCWGqQAAAAAAM8Ng9k570ayit0OK365Vn8yY2g0amdHlrkBcRNGylUACQAA</VsDebuggerCausalityData>
</s:Header>
<s:Body>
<IsServiceInitiated xmlns="http://tempuri.org/" />
</s:Body>
</s:Envelope>
After receive request: Action = http://tempuri.org/IBaseService/IsServiceInitiated Reply =
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">http://tempuri.org/IBaseService/IsServiceInitiated</a:Action>
<a:MessageID>urn:uuid:d14b0af4-81c3-46c7-9b38-83a8fb092028</a:MessageID>
<a:ReplyTo>
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
</a:ReplyTo>
<VsDebuggerCausalityData xmlns="http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink">uIDPoy1+4Je4rwBHkZsB8NCWGqQAAAAAAM8Ng9k570ayit0OK365Vn8yY2g0amdHlrkBcRNGylUACQAA</VsDebuggerCausalityData>
<a:To s:mustUnderstand="1">net.tcp://localhost:7780/CustomService</a:To>
</s:Header>
<s:Body>
<IsServiceInitiated xmlns="http://tempuri.org/">
</IsServiceInitiated>
</s:Body>
</s:Envelope>
我等待冻结的方法,终于得到了一个CommunicationException: 服务器没有提供有意义的回复;这可能是由合同不匹配、会话过早关闭或内部服务器错误引起的
新编辑:
太棒了!!! 当使用单独的应用程序启动服务器并且第一个应用程序将客户端连接到该服务器时,它可以工作并且消息检查器不会启动我的 AfterReceiveRequest 事件,但我得到了 BeforeReceiveReply 输出:
Before receive reply: Action = http://tempuri.org/IBaseService/IsServiceInitiatedResponse Reply =
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">http://tempuri.org/IBaseService/IsServiceInitiatedResponse</a:Action>
<a:RelatesTo>urn:uuid:bc055419-68d6-4f08-8170-0d1097e11d39</a:RelatesTo>
<a:To s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/anonymous</a:To>
</s:Header>
<s:Body>
<IsServiceInitiatedResponse xmlns="http://tempuri.org/">
<IsServiceInitiatedResult>true</IsServiceInitiatedResult>
</IsServiceInitiatedResponse>
</s:Body>
</s:Envelope>
再次编辑: 我注意到如果在显示 Windows 窗体之前打开服务器主机(“显示”方法),我的服务可以完美运行,但是如果在显示任何 Windows 窗体之后或在 Windows 窗体的构造函数中打开主机,则 WCF 方法调用freeze.......很奇怪......
【问题讨论】:
-
如果你在不同的线程上创建和打开你的 serviceHost 会发生什么?事情有效吗?在不同的 AppDomain 上呢?
-
那个是解决办法,但是我不明白为什么我不能在表单的同步上下文中启动主机并与它通信...