第13章 诊断

第13章 诊断

13.1 条件编译

预编译的指令见 4.16 预处理指令,我们这里的条件编译用到的指令有:

  • #if​​、#else​​、#endif​​、#elif​​

    条件编译指令可以进行 && ​、 || ​、 ! 运算。

预定义指令可以通过三种方式定义:

  1. 在文件中通过 #define 定义 Symbol
  2. 编译时传递 /define 参数
csc Program.cs /define:TESTMODE,PLAYMODE
  1. Visual Studio 在项目属性中定义编译 Symbol

13.1.2 Conditional​ 特性

Conditional​ 特性用法如下:

[Conditional ("LOGGINGMODE")]
static void LogStatus (string msg)
{string logFilePath = ...System.IO.File.AppendAllText (logFilePath, msg + "\r\n");
}

Conditional​ 特性指示编译器在所有调用 LogStatus​ 的地方使用 #if LOGGINGMODE ​ 进行包装,如果 LOGGINGMODE​ 没有定义,则 LogStatus​ 的调用在编译时将完全忽略,包括参数表达式的计算。即使 LogStatus​ 和调用者 不在同一程序集 该机制仍然有效。

C7.0 核心技术指南 第7版.pdf - p578 - C7.0 核心技术指南 第 7 版-P578-20240214151621

#if​ 相较 Conditional​ 的不足:

C7.0 核心技术指南 第7版.pdf - p577 - C7.0 核心技术指南 第 7 版-P577-20240214151902

13.2 Debug​ 和 Trace​ 类

Debug​ 和 Trace​ 类的方法都被 Conditional​ 进行了标注,区别如下:

  • 所有 Debug​ ​类中的方法都标记为 [Conditional("DEBUG")] ​​。
  • 所有 Trace​ ​类中的方法都标记为 [Conditional("TRACE")] ​​。

默认情况下,VS 在项目的 debug 配置中定义了 ** DEBUG ** 和 ** TRACE ** 符号,在 release 中仅定义了 ** TRACE ** 符号。

13.2.1 Fail​ 和 Assert​ 方法

Debug​ 和 Trace​ 类型都提供了 Fail ​和 Assert ​方法,区别如下:

  • Fail

    调用时将弹出对话框,询问“忽略”、“终止”还是“重试”。

  • Assert

    Assert​ 中的表达式返回 false​(意味着程序缺陷),将会自动调用 Fail​ 方法。

从技术上说,根据条件抛出异常也是一种断言,区别如下:

  • 抛出异常:反映了 调用者代码 缺陷
  • Assert​:反映了 当前方法的 缺陷

13.2.2 TraceListener​ 类

Debug​ 和 Trace​ 类的 Listeners​ 属性是 TraceListener ​ 实例的静态集合。它们负责处理 Write​、Fail​ 和 Trace​ 方法触发的信息。

默认情况下,该集合包含一个单独的监听器( DefaultTraceListener ​)。可以手动添加自定义监听器、移除默认监听器,以改变监听行为。既可以从零开始编写 TraceListener​(从 TraceListener​ 继承),也可使用以下预定义的监听器类型:

  1. TextWriterTraceListener ​​:将消息写入 Stream​ ​或者 TextWriter​​,或将消息追加到文件中。

    TextWriterTraceListener​ 类还进一步的划分为了:

    1. ConsoleTraceListener​​、
    2. DelimitedListTraceListener​​、
    3. XmlWriterTraceListener​​
    4. EventSchemaTraceListener​​.
  2. EventLogTraceListener ​​:将事件日志写入到 Windows 事件日志中。

  3. EventProviderTraceListener ​​:将事件写入操作系统的 Windows 事件追踪(EventTracingforWindows,ETW)子系统中(支持 WindowsVista 及其之后的操作系统)。

  4. WebPageTraceListener​​:写入某个 ASP.NET 页面。

C7.0 核心技术指南 第7版.pdf - p581 - C7.0 核心技术指南 第 7 版-P581-20240215094613

提纲
mindmap TraceListener预定义DefaultTraceListenerTextWriterTraceListenerEventLogTraceListenerEventProviderTraceListenerWebPageTraceListener常用成员FilterIndentLevelIndentSizeTraceOutputOptionsAutoFlush["Flush()"]["Close()"]
DefaultTraceListener

这个默认的监听器有两个关键特性:

  1. 当连接到调试器时(例如 Visual Studio 的调试器),将消息输出到 调试输出窗口 ;否则忽略消息内容。
  2. 当调用 Fail​ 方法时(或 Assert​ 失败时),不论是否附加了调试器,都 弹出对话框询问用户是继续、忽略还是重试(附加/调试)
EventLogTraceListener

该监听器将信息输出至 Windows 事件日志 ,不同的方法将输出不同类型的日志:

  1. 信息:

    Write​、Fail​、Assert

  2. 警告:

    TraceWarning

  3. 错误:

    TraceError

使用方式
Trace.Listeners.Clear();
Trace.Listeners.Add(new TextWriterTraceListener("D:\\trace.txt"));
TextWriter tw = Console.Out;
Trace.Listeners.Add(new TextWriterTraceListener(tw));
// 如下代码需用管理员权限运行
if (!EventLog.SourceExists("WinFormsApp1"))EventLog.CreateEventSource("WinFormsApp1", "Application");
Trace.Listeners.Add(new EventLogTraceListener("WinFormsApp1"));
TraceListener​ 的属性

TraceListener ​有如下属性进行自定义行为:

  1. Filter​​ ​属性

    TraceFilter​​ 类型,消息过滤器。可以使用其预定义子类(例如 EventTypeFilter​​、SourceFilter​​),也可以派生 TraceFilter​​ 并重写 ShouldTrace​​ 方法。

  2. IndentLevel​​、IndentSize​ ​属性

    用于 控制缩进

  3. TraceOutputOptions​ ​属性

    可以用来 写入额外的数据 ,调用 Trace ​​​ 方法时会应用 TraceOutputOptions​​​ 中的配置:

    TextWriterTraceListener tl = new TextWriterTraceListener (Console.Out);
    tl.TraceOutputOptions = TraceOptions.DateTime | TraceOptions.Callstack;
    ...
    Trace.TraceWarning("Orange alert");
    // 输出:
    // DiagTest.vshost.exe Warning: O : Orange alert
    //        DateTime=2007-03-08T05:57:13.6250000Z
    //        Callstack=    at System.Environment.GetStackTrace(Exception e,BooleanneedFileInfo)
    //        at System.Environment.get_StackTrace()    at..
    

13.2.3 刷新并关闭监听器

TextWriterTraceListener​ 会先缓存再写入,需要适时关闭或刷新监听器。方法有三:

  1. 使用 Debug(Trace).Flush ​ ​方法刷新监听器

  2. 关闭程序时调用 Debug(Trace).Close ​ ​方法

    该方法会隐式调用 Flush ​方法,并关闭文件句柄。

  3. 设置 Debut(Trace).AutoFlush ​ ​属性为 true

    每条消息后强制执行 Flush ​方法。

C7.0 核心技术指南 第7版.pdf - p582 - C7.0 核心技术指南 第 7 版-P582-20240215171006

13.3 调试器的集成

13.3.1 附加和断点

System.Diagnostics ​命名空间中的静态 Debugger ​类提供了与调试器交互的基本函数:

  1. Break

    断点。启动调试器并将进程附加在调试器上,并在当前执行点挂起。

  2. Launch

    除挂起外,和 Break ​无区别。

  3. Log

    向调试器窗口输出信息。

  4. IsAttached

    是否已有调试器附加到了当前应用程序上。

13.3.2 Debugger​ 特性

DebuggerStepThrough ​和 DebuggerHidden ​特性可以应用于方法构造器、和

  • DebuggerStepThrough

    允许调试器在 单步执行 时跳过某些代码,但如果在该代码中发生异常,调试器 仍会停留

  • DebuggerHidden

    调试器完全忽略这段代码,即使发生异常 也不停留

13.4 进程与线程处理

13.4.1 检查运行中的进程

Process.GetProcessXXX ​ ​方法可以获得相应进程(包含托管、非托管进程)。每一个 Process​ ​实例都拥有诸多属性:名称、ID、优先级、内存、处理器利用率、窗口句柄等。

Process.GetCurrentProcess ​ ​方法返回当前的进程。如果创建了额外的应用程序域,它们将共享同一个进程。

如果需要终止一个进程,可以调用 Kill ​方法。

13.4.2 在进程中检查线程

Process.Threads ​ ​属性可用于枚举其他进程内的所有线程,为 ProcessThread​ ​类型。该对象用于管理而非执行同步任务。ProcessThread​ ​对象提供了相应线程的诊断信息,并允许对某些属性进行控制,例如优先级和处理器亲和性。

13.5 StackTrace​ 和 StackFrame​ 类

StackTrace​ ​和 StackFrame​ ​类提供了执行调用栈的只读视图,主要用于诊断目的。StackTrace​ ​代表了 一个完整的调用栈 ,而 StackFrame​ ​代表了 调用栈中的一个单独的方法调用

获取当前线程调用栈(StackTrace​)的快照,方式有二:

  1. 使用 StackTrace 的无参构造器。

  2. 使用 StackTrace bool 参数构造器。

    若参数传入 true​,且存在 pdb 文件,StackTrace​ 将读取文件名、行号等数据。

获取调用帧(StackFrame​),方法有二:

  • StackTrace.GetFrame ​​

    获取特定调用帧

  • StackTrace.GetFrames ​​

    获取所有调用帧

获取整个 StackTrace ​基本信息的最简单的方法是调用 ToString ​方法。当然也可以手动操作,方式如下:

static void Main() { A(); }
static void A() { B(); }
static void B() { C(); }
static void C()
{StackTrace s = new StackTrace(true);Console.WriteLine("Total frames:   " + s.FrameCount);Console.WriteLine("Current method: " + s.GetFrame(0).GetMethod().Name);Console.WriteLine("Calling method: " + s.GetFrame(1).GetMethod().Name);Console.WriteLine("Entry method:   " + s.GetFrame(s.FrameCount - 1).GetMethod().Name);Console.WriteLine("Call Stack:");foreach (var f in s.GetFrames()){Console.WriteLine("  File:   " + f.GetFileName() +"  Line:   " + f.GetFileLineNumber() +"  Col:    " + f.GetFileColumnNumber() +"  Offset: " + f.GetILOffset() +"  Method: " + f.GetMethod().Name);}
}

其输出如下:

C7.0 核心技术指南 第7版.pdf - p585 - C7.0 核心技术指南 第 7 版-P585-20240215181746

C7.0 核心技术指南 第7版.pdf - p586 - C7.0 核心技术指南 第 7 版-P586-20240215224138

另见22.10 Suspend 和 Resume 方法,获取线程的调用栈信息

StackTrace stackTrace = null;
targetThread.Suspend();
try { stackTrace = new StackTrace (targetThread, true); }
finally { targetThread.Resume(); }

13.6 Windows 事件日志

标准 Windows 事件日志有三种,按名称分为:

  1. 应用程序
  2. 系统
  3. 安全

应用程序日志是大多数应用程序通常写入日志的地方。

13.6.1 写入事件日志

写入前需创建相应的日志源,创建后,调用 EventLog.WriteEntry​ 写入时需提供日志名称、源名称和消息数据

image

使用方式如下:

const string SourceName = "WinFormsApp1";
if (!EventLog.SourceExists(SourceName))EventLog.CreateEventSource(SourceName, "Application");
EventLog.WriteEntry(SourceName, "测试Windows日志", EventLogEntryType.Information);

13.6.2 读取事件日志

步骤如下:

  1. 传入日志名称(三种日志名称之一),实例化 EventLog​ 对象
  2. 指定日志所在计算机的名称(可选)
  3. 通过 Entries ​集合读取日志

使用方式如下:

EventLog log = new EventLog("Application");Console.WriteLine(log.Entries.Count);EventLogEntry last = log.Entries[log.Entries.Count - 1];
Console.WriteLine("Index:   " + last.Index);
Console.WriteLine("Source:  " + last.Source);
Console.WriteLine("Type:    " + last.EntryType);
Console.WriteLine("Time:    " + last.TimeWritten);
Console.WriteLine("Message: " + last.Message);

可以使用静态方法 EventLog.GetEventLogs​​(需要管理员权限)来枚举当前(或者其他)计算机的所有日志名称,以下代码通常情况下至少会打印“应用程序”、“安全”以及“系统”:

foreach (EventLog log in EventLog.GetEventLogs()) {Console.WriteLine(log.LogDisplayName);
}

13.6.3 监视事件日志

EntryWritten ​事件相当于一个钩子,可以在 Windows 事件日志被写入时获得通知。使用步骤如下:

  1. 实例化 EventLog ​并将它的 EnableRaisingEvents ​属性设置为 true
  2. 处理 EntryWritten ​事件。

代码如下:

static void Main()
{using (var log = new EventLog("Application")){log.EnableRaisingEvents = true;log.EntryWritten += DisplayEntry;Console.ReadLine();}
}static void DisplayEntry(object sender, EntryWrittenEventArgs e)
{EventLogEntry entry = e.Entry;Console.WriteLine(entry.Message);
}

Info

仅 .NET Framework 原生支持。

13.7 性能计数器

Windows 日志用于获取信息进行事后分析,性能计数器则提供运行时监控状态的能力。用本节提到的 API,我们可用在软件上显示 CPU 利用率、内存使用情况等。

image

13.7.1 遍历可用的计数器

以下示例将遍历计算机上的所有可用性能计数器。对于那些支持实例的计数器,则遍历每一个实例的计数器:

var cats = PerformanceCounterCategory.GetCategories();
foreach (var cat in cats) {Console.WriteLine("Category: " + cat.CategoryName);var instances = cat.GetInstanceNames();// 不支持实例的计数器,如CPU核心if(instances.Length == 0) {foreach(var ctr in cat.GetCounters())Console.WriteLine("  Counter: " + ctr.CounterName);}else {// 支持实例的计数器,如应用程序进程foreach (var instance in instances) {Console.WriteLine("  Instance: " + instance);if (cat.InstanceExists(instance))foreach (var ctr in cat.GetCounters(instance))Console.WriteLine("    Counter: " + ctr.CounterName);}}
}

性能计数器类别(Performance Counter Categories)

性能计数器类别是一组逻辑相关的性能计数器的集合。例如,“Processor​”是一个性能计数器类别,它包含了与 CPU 性能相关的计数器,如 CPU 的利用率。

实例(Instances)

某些性能计数器类别下的计数器可以有多个实例。实例通常对应于系统中的资源或对象,例如,在 “Processor​” 类别下,每个 CPU 核心可能是一个实例;在 “Process​” 类别下,每个运行中的进程都是一个实例。

性能计数器(Counters)

在给定的类别下,性能计数器是实际测量的指标。例如,在 “Processor​” 类别下,可能有 “% Processor Time”(处理器时间百分比)这样的计数器。

关于每个进程的性能计数器

不是每个进程都包含自己的性能计数器类别,而是某些性能计数器类别(如 “Process​”)包含了多个实例,每个实例对应于一个进程。这些实例下的计数器反映了该进程的性能指标,如 CPU 使用率、内存使用量等。因此,通过这种方式,你可以监控每个进程的性能。

13.7.2 检索(查看)性能计数器

查看性能计数器的方式如下:

  1. 实例化 PerformanceCounter ​对象

  2. 调用 NextValue ​或者 NextSample ​方法。

    • NextValue ​返回简单的 float ​值;
    • NextSample ​返回 CounterSample ​对象,该对象包含高级属性,例如 CounterFrequency​、TimeStamp​、BaseValue ​以及 RawValue​。

下面列举一些简单的用法:

获取 CPU 整体使用率
using (PerformanceCounter pc = new PerformanceCounter("Processor", "% Processor Time", "_Total"))Console.WriteLine(pc.NextValue());
获取当前进程内存消耗
var procName = Process.GetCurrentProcess().ProcessName;
using (PerformanceCounter pc = new PerformanceCounter("Process", "Private Bytes", procName))Console.WriteLine(pc.NextValue());

PerformanceCounter ​并没有公开 ValueChanged ​事件,因此如果需要监视各种变化则必须使用轮询的方法。

13.7.3 创建计数器并写入性能数据

自定义计数器常见场景有:

  1. 应用性能监控

    开发者可以为自己的应用程序创建自定义的性能计数器来监控关键操作的性能,例如,数据库查询的平均响应时间、每秒处理的事务数、队列长度等。这些数据可以帮助开发者了解应用程序在实际运行中的性能状况,并及时发现性能瓶颈。

  2. 系统健康检查

  3. 动态性能调优

  4. 软件测试和质量保证

  5. 安全监控

如下实例代码演示了如何创建分组该分组下的所有计数器

var category = "Nutshell Monitoring";var eatenPerMin = "Macadamias eaten so far";
var tooHard = "Macadamias deemed too hard";
if (!PerformanceCounterCategory.Exists(category))
{var cd = new CounterCreationDataCollection();cd.Add(new CounterCreationData(eatenPerMin,"Number of macadamias consumed, including shelling time",PerformanceCounterType.NumberOfItems32));cd.Add(new CounterCreationData(tooHard,"Number of macadamias that will not crack, despite much effort",PerformanceCounterType.NumberOfItems32));PerformanceCounterCategory.Create(category, "Test Category", PerformanceCounterCategoryType.SingleInstance, cd);
}

image

如果之后希望在该分组下添加更多的性能计数器,必须先调用 PerformanceCounterCategory.Delete​ 方法删除旧的分组。

C7.0 核心技术指南 第7版.pdf - p593 - C7.0 核心技术指南 第 7 版-P593-20240216163440

一旦性能计数器创建完成,就可以实例化 PerformanceCounter​,将 ReadOnly ​属性设置为 false​,并对 RawValue ​属性赋值来更新计数器的值。也可以使用 Increament ​和 IncreamentBy ​方法来更新现有的值:

var category = "Nutshell Monitoring";
var eatenPerMin = "Macadamias eaten so far";using(PerformanceCounter pc = new PerformanceCounter(category, eatenPerMin, ""))
{pc.ReadOnly = false;pc.RawValue = 1000;pc.Increment();pc.IncrementBy(10);Console.WriteLine(pc.NextValue());
}

image

13.8 Stopwatch​ 类

此处简单介绍几个Stopwatch​属性:

  • ElapsedTicks

    long​ 类型,返回计数值。

  • Stopwatch.Frequency

    long 类型,表示计数频率。ElapsedTicks​ 除以该值可用得到对应的秒数。

可以直接通过 Stopwatch.ElapsedMilliseconds​ 属性获得用时,更为简便。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/862363.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第14章 并发与异步

第14章 并发与异步 14.2 线程 进 程提供了程序执行的独立环境, 进 程持有 线 程,且至少持有一个 线 程。这些 线 程共享 进 程提供的执行环境。 14.2.1 创建线程 创建线程的步骤为:实例化 ​Thread​ ​ 对象,通过构造函数传入 ​ThreadStart​ ​ 委托。 调用 ​Thread…

Sqlserver With as 实现循环递归

一、脚本示例declare @Separator varchar(10), @str varchar(100) declare @l int, @i int select @Separator=,,@str=111,22,777,99,666 select @i = len(@Separator), @l = len(@str); with cte7 as ( select 0 a, 1 b union all select b, charindex(@Separator, @str, b)+@…

JAVA 分布式锁

分布式锁 JVM 自带的 synchronized 及 ReentrantLock 锁都是单进程内的,不能跨进程,如下,同时来个两个请求被分配到不同的tomcat,这种锁将失效:REDIS 实现分布式锁 可以借助 REDIS 的setnx 命令实现: https://blog.csdn.net/T_Y_F_/article/details/144238022 注:redis …

java8--类Scanner--文件内容输入--windows路径分隔符转义

try { Scanner in = new Scanner(Paths.get("C:\Users\Administrator\IdeaProjects\untitled2\src\test\myfile.txt"),"UTF-8"); } catch (IOException ioException) { ioException.printStackTrace(); }ps: 1.打印当前工…

[Windows] 启动 Windows Update 服务失败,报:Windows 无法启动 Windows Update 服务(位于 本地计算机 上) 错误 126:找不到指定的模块

1 问题描述现象1:Windows 10 家庭版-服务(services.msc)-启动 Windows Update 服务失败,报:"Windows 无法启动 Windows Update 服务(位于 本地计算机 上) 错误 126:找不到指定的模块"注: C:\Windows\System32\wuaueng.dll 文件存在注:注册表regedit:计算机\HKEY_L…

共享ubuntu系统宿主机的部分文件到win虚拟机--通过ISO文件挂载

安装genisoimage sudo apt-get update sudo apt-get install genisoimage将需要共享的文件放入指定文件夹 cp /path/to/your/file ~/iso_work/使用genisoimage生成新镜像 genisoimage -o /path/to/new.iso -J -R -V "NEW_ISO_LABEL" ~/iso_work/其中new.iso就是新镜像…

Luogu P9646 SNCPC2019 Paper-cutting 题解 [ 紫 ] [ manacher ] [ 贪心 ] [ 哈希 ] [ BFS ]

manacher 与贪心的好题。Paper-cutting:思维很好,但代码很构式的 manacher 题。 蒟蒻 2025 年切的第一道题,是个紫,并且基本独立想出的,特此纪念。 判断能否折叠 我们先考虑一部分能折叠需要满足什么条件。显然,这一部分需要是一个长度为偶数的回文串。 那么横向和纵向会…

深度学习基础理论————分布式训练(模型并行/数据并行/流水线并行/张量并行)

主要介绍Pytorch分布式训练代码以及原理以及一些简易的Demo代码 模型并行 是指将一个模型的不同部分(如层或子模块)分配到不同的设备上运行。它通常用于非常大的模型,这些模型无法完整地放入单个设备的内存中。在模型并行中,数据会顺序通过各个层,即一层处理完所有数据之后…

overleaf-Latex教程

1.领取免费服务器,推荐免费服务器(SanFengYun)见下图。2.安装宝塔面板,配置内网为127.0.0.1,访问外网地址。 3.可以在宝塔面板一键部署网站,输入自己的域名即可。 4.关键:安装docker,安装yum,设置github可以访问。 5.更换docker镜像,自带镜像无法访问 6.按照overleaf…

Sola的2024年度总结

前言 2024 这一年对我来说确实意义非凡,很想写点东西来记录一下这一年我的经历,算是第一次写年度总结了。 简短的记录一下我这一年。 现在?未来? 回忆起大一下最后一节体育课,体育老师让每个人想一个词来描述这个上半年,我给出的答案是 : 迷茫 。 现在来看,这个答案贯穿…

洛谷 P11487 「Cfz Round 5」Gnirts 10——题解

洛谷P11487「Cfz Round 5」Gnirts 10传送锚点摸鱼环节 「Cfz Round 5」Gnirts 10 题目背景 English statement. You must submit your code at the Chinese version of the statement.In Memory of \(\text{F}\rule{66.8px}{6.8px}\). 题目描述 题面还是简单一点好。给定 \(n, …

基于高德地图API在Python中实现地图功能的方法

本文介绍在高德开放平台中,申请、获取地图API的Key的方法;同时通过简单的Python代码,调取API信息,对所得Key的可用性加以验证~本文介绍在高德开放平台中,申请、获取地图API的Key的方法;同时通过简单的Python代码,调取API信息,对所得Key的可用性加以验证。首先,我们进入…