WPF程序卡死的一次解决过程

服务的背景: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,在不能动客户端的情况下,这个方法就行不通了。


评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注