服务的背景:NET8 的 WPF 程序,并侦听提供了 WCF 服务。
现象
发生:系统凌晨上线后,运行到第二天早上,突然用户说访问不了。
现象:日志是通过 log4net 打印到 mongo,发现出问题的时候,
只有入的日志 IDispatchMessageInspector.AfterReceiveRequest,
没有处理的日志(业务日志),
以及出的日志 IDispatchMessageInspector.BeforeSendReply。
找问题的过程
- 过程一、查日志
日志是通过 log4net 打印到 mongo,发现出问题的时候,
只有入的日志 IDispatchMessageInspector.AfterReceiveRequest,
没有处理的日志(业务日志),
以及出的日志 IDispatchMessageInspector.BeforeSendReply - 过程二、看资源图表
打开 grafana,查看当时系统资源占用情况,
CPU、内存、那些基本都够 - 过程三、初步推断
基于 1、2 的信息,首先明显可以感觉到是哪里发生了阻塞,同时初步推断不是由于资源不够导致的。 那这时比较大的可能是哪个地方不断抢用线程,导致线程不够处理。
但是能打印入的日志,并且没有进程的线程数据支撑,这个推断还不能比较确定。
但是此时有没有太多别的信息可供参考了,想着下次出问题再抓调用堆栈看看。 - 过程四、进一步分析日志
再仔细分析日志可能有收获。
于是多角度分析日志- 统计每分钟每个请求路径的请求次数,看看有没有异常。
- 查看出问题前后,哪个请求开始是没有响应的(每个请求有guid),诊断出问题可能出现的时刻。
- 这两板斧下来,发现出问题的时刻,刚好出现了一个请求,在此前后都是没出现过的,
直接拿出来请求试试,结果还真找到问题点了。 这个请求一被调用,整个WPF UI就卡死了,无法动,请求也不响应。
解决
过程分析
看了下代码,有个异步方法,用了同步 Task.Result。
很典型的 WPF UI 主线程卡死问题,就是主线程调用 Task.Result 的时候,
主线程被阻塞,在等待 Result 返回,此时主线程不可用,
而 Task 执行完想通知主线程唤醒原来的上下文继续干活,
但是主线程已经被阻塞了,是无法帮 Task 去唤醒原来的上下文的,
两边都在等对方,所以就卡死了。
所以,解决的方案就两种了。
一、让处理方法不要在主线程上跑
二、改成异步方法
解决方案一、
// 把监听端口绑定 WCF 端点的服务,在主线程以外的线程启动。
Thread thread = new Thread(async()=>{
var host = CreateHost();
await host.StartAsync();
});
thread.IsBackground = true;
thread.Start();
解决方案二、
// 原来的同步契约方法
[OperationContract]
public bool Discount(string num)
{
return BaseService.Discount(num).Result;
}
// 把契约方法改成异步
[OperationContract]
public async Task<bool> Discount(string num)
{
return await BaseService.Discount(num);
}
但是,方案二在我这里行不通,因为我们这里原来的方法定义上用到了 out 关键字,用来返回。
异步方法的定义不能有 out/ref,在不能动客户端的情况下,这个方法就行不通了。
发表回复