在使用WCF的时候,用来启动服务的Host方式有:Console,WinForm,IIS,Windows Service 4种。其中Winform 作为Host的同时,通常出于某种业务需求,除了充当Host以外还有自己的UI显示与操作。在做这种应用的时候,常常会发现WCF的调用会导致UI阻塞,使得服务端的Winform无法正常操作。
下面来看一个示例:服务端UI自己每1s显示一条系统时间数据,客户端每5s调用WCF服务获取时间数据并显示。
看看代码:
1. WCF, 服务实例使用 Single, GetData 中模拟复杂操作,延迟了5s。
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData();
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class Service1 : IService1
{
public string GetData()
{
// 模拟复杂操作,延迟5m
Thread.Sleep(5000);
return DateTime.Now.ToString("HH:mm:ss fff");
}
}
2. 服务端和客户端的代码。(为了简便,服务端和客户端通用一个Winform应用)
(1) 服务端(_host是ServiceHost的引用)
private void SetSever()
{
_host = new ServiceHost(typeof(WcfWinformHostLib.Service1));
_host.Open();
Text += "[Server]";
timer1.Interval = 1000;
timer1.Tick += (s, e) =>
{
var data = DateTime.Now.ToString("Server: HH:mm:ss.fff") + Environment.NewLine;
textBox1.Text = data + textBox1.Text;
};
}
(2)客户端(_client直接使用数据契约,通过ChannelFactory获取远程对象的引用)
private void SetClient()
{
var factory = new ChannelFactory<WcfWinformHostLib.IService1>(
new NetTcpBinding(),
"net.tcp://localhost:20001/WcfWinformHostLib/Service1");
_client = factory.CreateChannel();
Text += "[Client]";
timer1.Interval = 5000;
timer1.Tick += (s, e) =>
{
var data = "Client: " + _client.GetData() + Environment.NewLine;
textBox1.Text = data + textBox1.Text;
};
}
OK, 现在运行一下看看实际的运行情况:
1.启动服务端,这时候数据上看还是很正常的,每隔1s显示一条数据。
2.启动客户端,也是正常的每5s一条数据。
再看看服务端,这个时候数据已经不正常了,变成和客户端一下的5s一次了,而且拖拽服务端界面还觉得很卡,UI明显有阻塞。
开始找原因,第一眼你可能会觉得是否是 Service 的 InstanceContextMode = Single 的问题,实际证明就算修改为 PerCall 也不会任何改变,那会不会是重入模式设置的不正确呢?不是,修改为 ConcurrencyMode.Multiple 服务端应用的UI依然阻塞。经过多次调试,发现这个UI阻塞的罪魁祸首是 ServiceHost,猜想 ServiceHost 一些启动的监听操作会和 UI 的消息循环产生冲突。(谁知道具体的原因,请留言告知,多谢)。我们修改一下 ServiceHost 把它扔到一个线程里:
添加一个 ThreadServiceHost 类
public class ThreadServiceHost
{
const int SleepTime = 100;
private ServiceHost _serviceHost = null;
private Thread _thread;
private bool _isRunning;
public ThreadServiceHost(Type serviceType)
{
_serviceHost = new ServiceHost(serviceType);
_thread = new Thread(RunService);
_thread.Start();
}
void RunService()
{
try
{
_isRunning = true;
_serviceHost.Open();
while (_isRunning)
{
Thread.Sleep(SleepTime);
}
_serviceHost.Close();
((IDisposable)_serviceHost).Dispose();
}
catch (Exception)
{
if (_serviceHost != null)
_serviceHost.Close();
}
}
public void Stop()
{
lock (this)
{
_isRunning = false;
}
}
}
同时,修改服务端的启动代码:
_host = new ThreadServiceHost(typeof(WcfWinformHostLib.Service1));
再次启动服务端和客户端,这下运行正常了。
服务端:每 1s 一条数据, 客户端:每 5s 一条数据。