c#软件针对出现卡顿,运行不流程,崩溃等问题优化方案


前言

c#软件优化前后对比

相信很多的朋友在做项目的过程中,遇到功能越加越多,项目又频繁改动,原本还可以正常运行的程序,慢慢的明显出现了卡顿现象,就向视频中的一样,鼠标点击明显反应变慢了,点击太快卡顿非常明显。往往这时候束手无策,心理慌的不行,不知道哪里出了问题,也不知道从哪里找问题。别着急看完以下内容,相信大家对优化有了更深的了解,不再为卡顿与优化问题而发愁


一、程序运行卡顿的原因有那些?

  1. 了解原因之前我们来先了解软件执行原理。软件在启动过程中会一条一条的去执行你写的代码,当其中某一行或某一段代码(比如读取本地的图片,调用摄像头,或者写了一个死循环) 执行时间特别长的时候,我们再去手动点击软件里的某个按钮,就会出现卡顿现象。因为程序主线程代码没有执行完,而你又点击了按钮就相当于给程序又添加了一段要执行的代码。系统不会优先处理你点击了按钮的代码,它会继续等待之前的代码执行完。执行完之后才会执行你点击了按钮的代码。所以就出现了第一个卡顿的原因。
  2. 某代码片段执行时间长。既然这样我们是不是就可以用多线程来解决它呢?确实可以,但也要看线程如何使用,线程调用多了,也会堵塞通道,导致整个程序卡死。这就是第二个卡顿原因。
  3. 第三点是资源的回收,我们在执行程序过程中会产生很多的资源,比如创建了很多buff,很多的线程,实例化的很多对象,使用完之后没有及时销毁,导致内存占用越来越大从而导致卡顿。

二、如何排查问题?

优化之前首先要找到问题,如果只知道软件卡顿,但不知道哪里卡。那优化将无从下手。

1.断点法

Visual Studio编辑器进行断点,按f11进行一段段执行,观察哪个片段执行缓慢
在这里插入图片描述

2.片段返回法

执行到一半,不再往下继续,直接一个return返回,观察这段代码是否存在执行慢的问题,如果存在继续往上排查,如果不存在则继续往下排查。
【截图】

3.注释法

如果只是查看代码感觉某段代码会导致执行缓慢,可以将相关的代码注释掉继续执行,观察是否变快了。
在这里插入图片描述

4.计时器排查法

代码执行之前开始计时,每个代码片段后面打印时间,观察某个时间戳变化

private Stopwatch watch = new Stopwatch();

private void Form1_Load(object sender, EventArgs e)
{
	 		watch.Start();
            int z = 0;
            Console.WriteLine("第一段时间为:" + watch.ElapsedMilliseconds.ToString());
            for (int j = 0; j < 100; j++) // 循环个一千遍
            {
                Thread.Sleep(1);
            }
            Console.WriteLine("第二段时间为:" + watch.ElapsedMilliseconds.ToString());
            Thread.Sleep(2000); // 模拟代码片段执行需要两秒
            watch.Stop();
            Console.WriteLine("第三段时间为:" + watch.ElapsedMilliseconds.ToString());
            watch.Restart();
            LogHelper.WriteLog("软件启动就执行日志插入"); // 连续测试个10遍
            watch.Stop();
            Console.WriteLine("第一次插入 日志时间为" + watch.ElapsedMilliseconds.ToString());
}

在这里插入图片描述
可以很明显的看到耗时最长的代码在哪里。

5.渲染排查法

这种情况比较特殊,通过代码很难排查出来。有的在程序运行很长的时间会出现这样的问题。比如图表数据随着时间的推移不断的叠加。也有的是数据过于庞大导致渲染卡顿。
排查方式也很简单,将这些需要渲染的控件或相关代码进行隐藏,再观察执行速度的变化。

三、优化方向有那些?又该如何优化?

根据上面的步骤相信大家很快就能找到问题,找到问题之后我们可以通过以下几点开始进行优化。

1.初始化优化

初始化分两种情况,如果该对象需要等页面加载完才能执行,

  • 在form_load直接进行初始化。
  • 如果不需要界面支持,页面刚加载就进行初始化。
  • 注意:不可在每次点击的时候频繁的初始化。初始化尽量一次性解决。如果时间太长,可开线程来进行初始化。

2.for循环优化

讲到这里优化的情况有很多种,比如for循环里不断的进行初始化,for循环里不断的读取电脑图片,for循环里不断的渲染,for循环里不断的操作数据库等等。
无论会遇到上面那个原因,总之只要发现在for循环里的某个代码片段,存在执行时间很长的情况,我们都要进行处理。比如for循环一次有一个代码需要执行100ms,如果数据量庞大有一百条,那么整个执行时间就是100*100也就是10秒,程序就会卡顿十秒。

  • 解决方案:用数组将这些循环之后的数据存储起来,然后再开线程执行。比如数据库操作。先看案例:
public void ChartsShow(string name)
        {
            Console.WriteLine("渲染图表操作" + name);
        }
        public void ExecuteSql(string sqlStr)
        {
            Thread.Sleep(10); // 模拟数据库操作一次需要10ms
            Console.WriteLine("执行数据库操作" + sqlStr);
        }

        public void testSql()
        {
            for (int i = 0; i < 1000; i++)
            {
                string sqlStr = @"insert into DefectInfo..." + "name = name" + i ; //创建数据库
                ChartsShow("name" + i);// 渲染图表
                ExecuteSql(sqlStr); // 执行数据库语句
            }
        }

在上面的代码片段中,当我们执行tesSql方法时,由于数据库执行存在延时,所以会导致图表渲染慢,而且还在主线程执行中所以程序也会卡顿。
这时我们可以将数据库语句存储到数组里,之后再开一个线程,线程里for循环去执行数据库操作。这样即使再慢,也不会影响到软件卡顿。代码如下:

 public string[] sqlArr;
        public void ChartsShow(string name)
        {
            Console.WriteLine("渲染图表操作" + name);
        }
        public void ExecuteSql(string sqlStr)
        {
            Thread.Sleep(10); // 模拟数据库操作一次需要10ms
            Console.WriteLine("执行数据库操作" + sqlStr);
        }

        public void ExecuteAllSql()
        {
            int len = sqlArr.Length;
            for (int i = 0; i < len; i++)
            {
                ExecuteSql(sqlArr[i]);
            }
        }

        public void testSql()
        {
            sqlArr = new string[1000];
            for (int i = 0; i < 1000; i++)
            {
                string sqlStr = @"insert into DefectInfo..." + "name = name" + i ; //创建数据库
                ChartsShow("name" + i);// 渲染图表
                sqlArr[i] = sqlStr; // 所有的sql语句存储起来
            }
            Thread th = new Thread(ExecuteAllSql); // 开线程执行这些数据库语句
            th.Start();
        }

有人会觉得那还不如直接在for循环里开线程处理。错误示范代码如下:

 public void testSql()
        {
            sqlArr = new string[1000];
            for (int i = 0; i < 1000; i++)
            {
                string sqlStr = @"insert into DefectInfo..." + "name = name" + i ; //创建数据库
                ChartsShow("name" + i);// 渲染图表
                Thread th = new Thread(() => {
                    ExecuteSql(sqlStr);
                });
                th.Start();
            }
        }

这块要注意,for循环里最好不要开线程,如果这个循环100条,那就会开一百个线程,线程频繁开启,线程也会自动销毁,整个过程会加大性能损耗,线程越来越多也会引起堵塞,使得整个for循环同样变慢。同样会引起卡断。

3.渲染优化

如图表的渲染,如数据越发的庞大,运行也会越来越慢。
在这里插入图片描述

  • 自动初始渲染:
    相信很多朋友会遇到这样的一个问题,第一次启动时慢很多,一段时间之后慢慢就变快了。这是因为控件被渲染的同时,添加了缓存。后续再渲染的时候程序会自动向缓存里取数据。所以我们在程序启动时可以先制作一些假数据,进行渲染。当然最好再添加一个遮罩不让用户看到,我们的程序在后台默默的渲染,渲染完之后再清除掉假数据,并去掉遮罩。这样我们就有了缓存。
  • 动态渲染:
    比如有1000条数据要显示,界面只给它显示100条,上100条与下100条进行缓存,滑动的时候再进行加载,并清空掉多余隐藏的数据。
  • 数据分页:
    数据过于庞大可添加分页控件,进行分页处理。
  • 异步渲染:
    如果数据庞大,加载页面的时候要渲染图表,可用线程进行异步渲染,添加一个加载中遮罩,用Invoke或BebinInvoke进行委托渲染。比如这边更新数据库的同时,修改panel的宽度。代码如下:
 public void ExecuteAllSql()
        {
            int len = sqlArr.Length;
            for (int i = 0; i < len; i++)
            {
                ExecuteSql(sqlArr[i]);
                panel1.Invoke(((new EventHandler(delegate // 委托渲染panel控件 修改控件的宽度
                {
                    panel1.Width = i;
                }))));
            }
        }

        public void testSql()
        {
            sqlArr = new string[1000];
            for (int i = 0; i < 1000; i++)
            {
                string sqlStr = @"insert into DefectInfo..." + "name = name" + i ; //创建数据库
                ChartsShow("name" + i);// 渲染图表
                sqlArr[i] = sqlStr; // 所有的sql语句存储起来
            }
            Thread th = new Thread(ExecuteAllSql);
            th.Start();
        }

4.线程优化

  • 耗时问题:上面几条中多次提到了线程,总结一条就是耗时的代码片段都可用线程来处理。
  • 顺序问题:如果开了线程之后程序要按顺序执行,可在线程里进行lock锁定处理。但也要注意数量,后续的线程会排序执行。
  • 控制好线程数量:不要在for循环里创建线程,最好是线程里放for循环。
  • 控件委托渲染:涉及到控件部分一定要注意,必须要进行委托如invoke方法。否则会直接报错。

5.底层插件或第三方插件调用优化

相信大家调用第三方的插件会非常头疼,他们提供的方法很多,执行起来也有很多耗时的问题。

  • 减少调用:我们要尽量减少这些调用。比如提前初始化处理好,然后将第三方插件存储到一个类中
  • 使用入口合一:如果第三方插件不支持反复new一个对象,可创建静态类来存储这个插件。所有的窗口调用,都只对这个静态类进行处理,这样既可很好的避免第三方插件资源占用问题。
    比如这里创建两个摄像机。无论有多少个窗口,都统一通过这个类来调用摄像机。

    在这里插入图片描述
    在这里插入图片描述

6.代码执行逻辑优化

这方面的优化有很多

  • 相同名称的变量:检查重复的变量名称,重命名或删掉
  • 重复且无用的代码:
    比如重复的new一个对象或方法里出现多余的操作代码。比如设置用户名称的方法里,添加了一个无用打开摄像头的方法。引起的设置过程缓慢。
public string SetName(string name){
	openCamare(); // 多余的代码 延缓一下代码的执行
	user.name = name;
	return name;
}
  • 代码顺序:
    再比如要设置摄像头名称,如果摄像头还没有打开,没有获取到摄像机这个对象,就进行直接设置名称,由于camare对象没有实例化而崩溃。
public string SetName(string name){
	camare.name = name; // 直接报错或无效,相机信息没获取到
	openCamare(); 
	return name;
}

7.逻辑判断优化

  • 空指针判断:代码执行之前 进行各种空指针判断,不满足条件的不让它继续往下跑。
    比如摄像头没有打开,我们要获取摄像机图片,可以在获取之前就进行判断。如果没有这个判断,可能会因为摄像头没连接,导致获取图片非常缓慢,引起卡顿的问题。
if(isCamareOpen != null){ // 如果相机打开了则进行以下操作,否则直接返回。
	camare.GetImg();
}else{
	return;
}

8.缓存优化

  • 数据存储:比如读取的本地图片可用变量进行存储起来。如下代码:
System.Drawing.Image img = System.Drawing.Image.FromFile(marker_path);
System.Drawing.Image bmp = new System.Drawing.Bitmap(img);

这样我们在使用过程中直接用bmp字段来使用图片。而不是每次使用都需要反复打开

Stream s = File.Open(marker_path, FileMode.Open); // 打开图片
  • 状态存储:这里以文件路径判断为例,如果每次都要用底层文件夹Directory去读取文件来判断路径是否存在,配置低的电脑可能会存在耗时慢的问题。
for(int i=0;i<100;i++){
	if (!Directory.Exists(url))     // 返回bool类型,存在返回true,不存在返回false
	 {
	     Directory.CreateDirectory(url);      //不存在则创建路径
	     oldSliptImgPath = url; // 重复的路径就不用去检测
	 }
 }

我们可以创建一个变量来判断这个文件路径是否已创建,如oldSliptImgPath代码如下:

private static string oldSliptImgPath = ""; // 历史路径 用于检测路径是否相同
public static string GetSplitImgPath(string name) // 获取最终截图路径 name为券号名称
        {
            string url = savaSliptImgPath + "/" + name + "/";
            if (oldSliptImgPath != url || oldSliptImgPath == "") // Directory.Exists 这个方法不要重复调用文件夹管理系统去检测,影响速度。检测一次之后用公共变量检测
            {
                if (!Directory.Exists(url))     // 返回bool类型,存在返回true,不存在返回false
                {
                    Directory.CreateDirectory(url);      //不存在则创建路径
                    oldSliptImgPath = url; // 重复的路径就不用去检测
                }
            }
            return url;
        }

总结

总之可通过问题排查几点想尽办法先找到问题,然后再根据优化方向几点进行处理问题。更多优化欢迎大家留言补充。

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

web前端神器

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值