LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

让你的Winform程序坚如磐石:全局异常捕获实战

admin
2025年2月5日 11:28 本文热度 27

一、引言:程序也会 “闹脾气”

家人们,咱就是说,有没有这样的经历:满心欢喜打开一个桌面应用程序,准备大干一场,结果操作没几下,突然弹出一个 “程序已停止工作” 的窗口 ,瞬间让人心态崩了!这其实就是程序在运行过程中遇到了未处理的异常,直接 “撂挑子” 不干啦。

对于咱们开发 Winform 程序的小伙伴来说,这种情况更是不能容忍。一个小小的异常,可能就会让用户对我们的软件失去信心。所以,今天就来和大家好好唠唠 Winform 程序中的全局异常捕获处理,让程序变得更 “坚强”,不再轻易 “闹脾气”!

二、认识异常:程序中的 “小怪兽”

(一)什么是异常

在程序的世界里,异常就像是突然冒出来的 “小怪兽” ,阻挡程序顺利运行。简单来说,异常就是程序在执行过程中出现的错误情况。当程序遇到一些不符合预期的条件,比如找不到文件、无法进行类型转换,或者内存不足时,就会抛出异常。这些异常如果不加以处理,就会导致程序的运行中断,就像汽车在行驶过程中突然爆胎,不得不停下来一样。

(二)异常的类型和危害

在 Winform 开发中,常见的异常类型有很多。比如,空引用异常(NullReferenceException),这是最常见的异常之一。当你试图访问一个值为 null 的对象的属性或方法时,就会抛出这个异常。想象一下,你手里拿着一个空盒子,却想从里面拿出东西,肯定是拿不到的,程序就会抛出异常来提醒你。又比如类型转换异常(InvalidCastException),当你尝试将一个对象转换为不兼容的类型时,就会出现这个问题。就好比你把苹果当成橙子,强行要把苹果榨成橙汁,这显然是不行的 。

这些异常如果不处理,危害可不小。最直接的就是导致程序崩溃,用户正在使用软件,突然程序就关闭了,这体验感简直太差了。还可能导致数据丢失,比如用户正在输入重要信息,结果因为异常程序崩溃,之前输入的数据没保存下来,用户肯定会很生气。所以,为了让程序稳定运行,给用户提供良好的体验,我们必须要处理这些异常。

三、全局异常捕获:给程序穿上 “防护服”

(一)什么是全局异常捕获

在 Winform 程序中,全局异常捕获就像是给程序安装了一个 “超级护盾” ,它可以捕获整个应用程序中未处理的异常。简单来说,就是不管程序的哪个部分出现了异常,只要没有被局部的 try - catch 块捕获,全局异常捕获机制就会发挥作用,把这些 “漏网之鱼” 异常给抓住。

(二)为什么要使用全局异常捕获

  1. 提高程序稳定性
    :当程序遇到异常时,如果没有全局异常捕获,很可能就会直接崩溃。而有了全局异常捕获,就可以避免程序因为一些意外的异常而突然终止,让程序更加稳定地运行。就好比给房子加固了地基,房子就不容易因为一点小震动而倒塌。
  1. 增强用户体验
    :想象一下,用户在使用我们开发的 Winform 程序时,如果频繁遇到程序崩溃的情况,肯定会对这个程序失去信心。通过全局异常捕获,我们可以在程序出现异常时,给用户一个友好的提示,比如 “很抱歉,程序出现了一点小问题,请稍后再试” ,而不是让用户看到一个莫名其妙的错误窗口,这样可以大大提升用户体验。
  1. 方便错误排查
    :全局异常捕获不仅可以捕获异常,还可以记录异常的详细信息,比如异常发生的时间、异常类型、异常信息以及堆栈调用等。这些信息对于我们开发人员来说,就像是破案的线索,可以帮助我们快速定位和解决问题。当程序出现问题时,我们可以根据这些记录的异常信息,迅速找到问题所在,提高开发效率。

四、实战演练:打造异常捕获 “神器”

(一)前期准备

在开始实战之前,先确保我们的开发环境已准备就绪。这里我使用的是 Visual Studio 2022 ,它功能强大,能为我们的开发工作提供很多便利。.NET Framework 版本为 4.8,这个版本兼容性较好,能满足大多数 Winform 项目的需求。如果你还没有安装这些工具,可以前往微软官方网站进行下载安装。

(二)关键代码实现

  1. UI 线程异常捕获

在 Winform 中,UI 线程负责处理用户界面的交互和更新。为了捕获 UI 线程中的异常,我们可以使用 Application.ThreadException 事件。下面是一段示例代码:

// 设置应用程序处理异常方式:ThreadException处理

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

// 处理UI线程异常

Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);

在这段代码中,Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException) 这行代码设置了应用程序处理未处理异常的模式,这里设置为捕获异常。Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException) 则是为Application.ThreadException事件添加了一个处理程序,当 UI 线程中出现未处理的异常时,就会调用Application_ThreadException方法。

下面是Application_ThreadException方法的具体实现:

static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)

{

string str = "";

string strDateInfo = "出现应用程序未处理的异常:" + DateTime.Now.ToString() + "\r\n";

Exception error = e.Exception as Exception;

if (error!= null)

{

str = string.Format(strDateInfo + "异常类型:{0}\r\n异常消息:{1}\r\n异常信息:{2}\r\n",

error.GetType().Name, error.Message, error.StackTrace);

}

else

{

str = string.Format("应用程序线程错误:{0}", e);

}

// 写日志

WriteLog.WriteErrLog(str);

MessageBox.Show("发生致命错误,请及时联系作者!", "系统错误", MessageBoxButtons.OK, MessageBoxIcon.Error);

}

在这个方法中,首先获取当前时间,构建异常信息的开头部分。然后通过e.Exception获取异常对象,进一步获取异常类型、消息和堆栈跟踪信息,并将这些信息格式化为一个字符串。接着调用WriteLog.WriteErrLog(str)方法将异常信息写入日志文件,方便后续排查问题。最后使用MessageBox.Show方法弹出一个提示框,告知用户程序出现了错误。

  1. 非 UI 线程异常捕获

对于非 UI 线程中的异常,我们可以使用 AppDomain.CurrentDomain.UnhandledException 事件来捕获。代码如下:

// 处理非UI线程异常

AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

同样,这里为AppDomain.CurrentDomain.UnhandledException事件添加了一个处理程序CurrentDomain_UnhandledException。下面是该方法的实现:

static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)

{

string str = "";

Exception error = e.ExceptionObject as Exception;

string strDateInfo = "出现应用程序未处理的异常:" + DateTime.Now.ToString() + "\r\n";

if (error!= null)

{

str = string.Format(strDateInfo + "Application UnhandledException:{0};\\n\\r堆栈信息:{1}", error.Message, error.StackTrace);

}

else

{

str = string.Format("Application UnhandledError:{0}", e);

}

// 写日志

WriteLog.WriteErrLog(str);

MessageBox.Show("发生致命错误,请停止当前操作并及时联系作者!", "系统错误", MessageBoxButtons.OK, MessageBoxIcon.Error);

}

这个方法和 UI 线程异常捕获的方法类似,也是获取异常信息,记录日志并弹出提示框。不同的是,这里通过e.ExceptionObject获取异常对象,因为非 UI 线程的异常处理方式和 UI 线程略有不同。

  1. 异常信息处理

在捕获到异常后,对异常信息进行整理是很重要的一步。我们可以获取异常的类型、消息、堆栈跟踪等信息。例如,在前面的代码中,通过error.GetType().Name获取异常类型的名称,error.Message获取异常的具体消息,error.StackTrace获取异常发生时的堆栈跟踪信息。这些信息可以帮助我们快速定位问题所在,比如异常是在哪个类、哪个方法中发生的 。通过将这些信息记录到日志文件中,我们可以在程序出现问题后,通过查看日志来分析问题,从而更好地解决问题。

(三)完整代码示例

下面是一个完整的包含全局异常捕获处理的 Winform 程序代码示例:

using System;

using System.Windows.Forms;

using System.IO;

using System.Text;

namespace WinFormExceptionHandling

{

static class Program

{

/// <summary>

/// 应用程序的主入口点。

/// </summary>

[STAThread]

static void Main()

{

try

{

// 设置应用程序处理异常方式:ThreadException处理

Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

// 处理UI线程异常

Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);

// 处理非UI线程异常

AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

Application.EnableVisualStyles();

Application.SetCompatibleTextRenderingDefault(false);

Application.Run(new Form1());

}

catch (Exception ex)

{

string str = "";

string strDateInfo = "出现应用程序未处理的异常:" + DateTime.Now.ToString() + "\r\n";

if (ex!= null)

{

str = string.Format(strDateInfo + "异常类型:{0}\r\n异常消息:{1}\r\n异常信息:{2}\r\n",

ex.GetType().Name, ex.Message, ex.StackTrace);

}

else

{

str = string.Format("应用程序线程错误:{0}", ex);

}

// 写日志

WriteLog.WriteErrLog(str);

MessageBox.Show("发生致命错误,请及时联系作者!", "系统错误", MessageBoxButtons.OK, MessageBoxIcon.Error);

}

}

static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)

{

string str = "";

string strDateInfo = "出现应用程序未处理的异常:" + DateTime.Now.ToString() + "\r\n";

Exception error = e.Exception as Exception;

if (error!= null)

{

str = string.Format(strDateInfo + "异常类型:{0}\r\n异常消息:{1}\r\n异常信息:{2}\r\n",

error.GetType().Name, error.Message, error.StackTrace);

}

else

{

str = string.Format("应用程序线程错误:{0}", e);

}

// 写日志

WriteLog.WriteErrLog(str);

MessageBox.Show("发生致命错误,请及时联系作者!", "系统错误", MessageBoxButtons.OK, MessageBoxIcon.Error);

}

static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)

{

string str = "";

Exception error = e.ExceptionObject as Exception;

string strDateInfo = "出现应用程序未处理的异常:" + DateTime.Now.ToString() + "\r\n";

if (error!= null)

{

str = string.Format(strDateInfo + "Application UnhandledException:{0};\\n\\r堆栈信息:{1}", error.Message, error.StackTrace);

}

else

{

str = string.Format("Application UnhandledError:{0}", e);

}

// 写日志

WriteLog.WriteErrLog(str);

MessageBox.Show("发生致命错误,请停止当前操作并及时联系作者!", "系统错误", MessageBoxButtons.OK, MessageBoxIcon.Error);

}

}

public class WriteLog

{

public static void WriteErrLog(string str)

{

string path = Application.StartupPath + @"\ErrorLog.txt";

using (StreamWriter sw = new StreamWriter(path, true, Encoding.UTF8))

{

sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " " + str);

sw.Close();

}

}

}

}

在这个示例中,Program类是程序的入口点。在Main方法中,首先设置了异常处理模式,然后分别为 UI 线程和非 UI 线程的异常添加了处理程序。接着启用可视化样式,设置文本渲染默认值,并运行主窗体Form1。如果在Main方法中发生了异常,也会进行相应的处理,记录日志并弹出提示框。WriteLog类负责将异常信息写入日志文件,日志文件名为ErrorLog.txt,保存在应用程序的启动目录下。每写入一条日志,都会包含当前时间和具体的异常信息 。通过这个完整的示例,希望大家能更好地理解和掌握 Winform 全局异常捕获处理的实现。

五、优化与拓展:让 “神器” 更强大

(一)异常日志记录

在前面的代码中,我们已经简单实现了将异常信息写入日志文件的功能。但在实际应用中,我们还可以对异常日志记录进行进一步优化。比如,使用专业的日志记录框架,像 Log4net,它功能更强大,配置更灵活。

使用 Log4net,首先要在项目中添加对它的引用。然后在配置文件(如 App.config)中进行配置,示例如下:

<configuration>

<configSections>

<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />

</configSections>

<log4net>

<appender name="FileAppender" type="log4net.Appender.FileAppender">

<file value="ErrorLog.log" />

<appendToFile value="true" />

<layout type="log4net.Layout.PatternLayout">

<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />

</layout>

</appender>

<root>

<level value="ALL" />

<appender-ref ref="FileAppender" />

</root>

</log4net>

</configuration>

在这段配置中,我们定义了一个名为FileAppender的日志输出器,它将日志输出到ErrorLog.log文件中。appendToFile属性设置为true,表示每次记录日志时,会追加到文件末尾,而不是覆盖原有内容。layout部分定义了日志的格式,包括时间、线程、日志级别、记录器名称和消息内容。

在代码中使用 Log4net 记录异常日志也很简单,示例如下:

using log4net;

using System.Reflection;

public class Program

{

private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

static void Main()

{

try

{

// 其他代码

}

catch (Exception ex)

{

log.Error("发生未处理的异常", ex);

MessageBox.Show("发生致命错误,请及时联系作者!", "系统错误", MessageBoxButtons.OK, MessageBoxIcon.Error);

}

}

}

在这个示例中,首先通过LogManager.GetLogger方法获取一个日志记录器,然后在捕获到异常时,使用log.Error方法记录异常信息,第一个参数是自定义的错误描述,第二个参数是异常对象。这样,异常信息就会按照我们在配置文件中定义的格式记录到日志文件中,方便我们后续查看和分析问题。

(二)自定义异常处理界面

默认的异常提示框(如MessageBox.Show弹出的框)虽然能简单地告知用户程序出现了错误,但不够美观和个性化。我们可以创建一个自定义的异常处理界面,让程序在捕获到异常时,以更友好、更专业的方式向用户展示错误信息。

首先,创建一个新的 Winform 窗体,命名为ExceptionForm.cs。在这个窗体上,我们可以添加一些控件,如一个显示错误信息的文本框,一个用于关闭窗体的按钮,以及一些美化界面的图片或标签等。

示例代码如下:

public partial class ExceptionForm : Form

{

public ExceptionForm(string errorMessage)

{

InitializeComponent();

txtErrorMessage.Text = errorMessage;

}

private void btnClose_Click(object sender, EventArgs e)

{

this.Close();

}

}

在这段代码中,ExceptionForm类的构造函数接收一个errorMessage参数,用于显示具体的异常信息。在构造函数中,将errorMessage赋值给文本框txtErrorMessage。当用户点击关闭按钮btnClose时,调用this.Close()方法关闭窗体。

然后,在捕获异常的地方,修改代码,使用自定义的异常处理界面,而不是原来的MessageBox.Show。例如,在Application_ThreadException方法和CurrentDomain_UnhandledException方法中,将MessageBox.Show替换为:

ExceptionForm exceptionForm = new ExceptionForm(str);

exceptionForm.ShowDialog();

这样,当程序捕获到异常时,就会弹出我们自定义的异常处理界面,向用户展示详细的错误信息,让用户感受到我们对程序的用心,提升用户对程序的好感度。

六、注意事项:避开异常捕获的 “陷阱”

(一)避免过度捕获

在设置全局异常捕获时,一定要注意避免过度捕获异常。虽然全局异常捕获很强大,但如果捕获得太宽泛,把所有异常都一股脑儿地抓进来,可能会隐藏真正的问题。比如说,在一个数据处理的方法中,可能会出现多种类型的异常,像数据格式错误、数据库连接失败等。如果我们在全局异常捕获中没有对这些异常进行区分,只是简单地记录一条 “发生异常” 的日志,那么当程序出现问题时,我们很难从这条简单的日志中判断出到底是哪里出了问题,这会给程序的调试带来很大的困难 。所以,在捕获异常时,要尽量做到精准捕获,针对不同类型的异常进行不同的处理,这样才能更好地定位和解决问题。

(二)合理处理异常

捕获到异常后,如何处理异常也是很关键的。不能简单地捕获了异常,然后就什么都不做,或者只是简单地显示一个错误信息,这是远远不够的。我们要根据具体的异常情况,进行合理的处理。比如,如果是因为网络连接问题导致的异常,可以尝试重新连接网络;如果是文件读写错误,可以提示用户检查文件路径是否正确,或者尝试创建一个新的文件。在处理异常时,要保证程序的健壮性,不能因为一个异常的出现,就影响到整个程序的其他功能。要尽量让程序在出现异常后,能够继续稳定地运行,或者至少给用户提供一个友好的提示,让用户知道该如何操作,这样才能提升程序的质量和用户体验 。

七、总结与展望:让程序更稳定

通过今天的分享,家人们应该已经深刻认识到全局异常捕获处理在 Winform 开发中的重要性啦!它就像是程序的 “保护神”,能够大大提高程序的稳定性,让我们的程序在面对各种异常情况时,都能保持良好的运行状态,给用户带来更好的体验。

从认识异常这个程序中的 “小怪兽”,到学会使用全局异常捕获给程序穿上 “防护服”,再到一步步实现全局异常捕获处理的代码,以及对它进行优化和拓展,我们一起走过了一段充实的学习之旅。在这个过程中,我们掌握了 UI 线程和非 UI 线程异常捕获的方法,学会了如何处理异常信息,还了解了如何优化异常日志记录和创建自定义异常处理界面 。


阅读原文:原文链接


该文章在 2025/2/5 18:35:44 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved