Stopwatch 类
简介
Stopwatch
类隶属与System.Diagnostics
命名空间中,该类提供了两套不同的高精度解决方案,用于计算某段程序运行时的精确到毫秒的时间。
该类提供的测量运行时间的解决方案可以看作使用Environment.TickCount
属性测量法的稳定版。
当使用Stopwatch
类时,需显式引用System.Diagnostics
命名空间(如果使用Visual Studio IDE,则会在使用类中方法和属性时自动补全)
解决方案1:测量的最简方式
代码例
该方法将通过实例化该类并调用类中方法以实现测量运行时间的效果
using System;
using System.Diagnostics;
class Program
{
static void Main()
{
//实例化Stopwatch类
Stopwatch stopwatch = new();
//启动计时器
stopwatch.Start();
// 假设有一段代码运行了1000毫秒
System.Threading.Thread.Sleep(1000);
//暂停计时器
stopwatch.Stop();
//将计时器中的值记录于类型为TimeSpan的变量中
TimeSpan ti = stopwatch.Elapsed;
Console.WriteLine(ti.ToString());
}
//其将输出00:00:01.0078401左右的值
}
使用方法
该解决方案使用的方法列表如下:
前缀 | 方法名 | 用途 | 说明 |
---|---|---|---|
public void | Start() | 启动计时器 | 计时器的值将会记录到Stopwatch类属性中 |
public void | Stop() | 暂停计时器 |
在以上代码例中,我们在实例化Stopwatch
类后,通过Start()
方法启动计时器,然后在我们执行完代码后,通过Stop()
方法暂停计时器,然后调用类中记录时间的属性Elapsed
进行输出。
当计时器运行时,属性Elapsed
将会开始计时,并将时间信息以时、分钟、秒、毫秒
的简单形式进行保存。
- 需要注意的是:
Stop()
方法会暂停计时器而不是停止计时器,所以如果在之后的代码中调用Start()
方法并记录时间的话,前后两段的时间将会叠加。
以下代码例说明了这一点:
using System;
using System.Diagnostics;
class Program
{
//说明“Stop()方法会暂停计时器而不是停止计时器”的代码例
static void Main()
{
Stopwatch stopwatch = new();
//第一段代码
stopwatch.Start();
System.Threading.Thread.Sleep(1000); //假设有一段代码运行了1000毫秒
stopwatch.Stop();
//输出第一段的运行时间
Console.WriteLine(stopwatch.Elapsed.ToString());
//第二段代码
stopwatch.Start(); //此处重新调用了Start()方法
System.Threading.Thread.Sleep(1000);
stopwatch.Stop();
//输出第二段的运行时间
Console.WriteLine(stopwatch.Elapsed.ToString());
}
//其将输出
//00:00:01.0021395
//00:00:02.0060838
//以上输出的值有一定的摆动区间
}
使用属性
存储值
该代码例使用了属性Elapsed
,该属性用于承载计时器计算的时间,并将其作为TimeSpan
类型的变量存储。
TimeSpan
变量类型是一个结构体,包含了从天到纳秒的时间值,并将其存储为内部属性。
具体而言如下:
属性名 | 存储部分 |
---|---|
Days | 天数 |
Hours | 小时 |
Minutes | 分钟 |
Seconds | 秒 |
Milliseconds | 毫秒 |
Ticks | 纳秒(一百纳秒为一个单位) |
当调用Elapsed.ToString()
时,其将会把Hours
、Minutes
、Seconds
、Milliseconds
包装为Hours:Minutes:Seconds.Milliseconds
,即00:00:00.0000000
的形式
- 可以对
Elapsed
中的特定属性单独进行输出:
例如可以将代码例中第23行的输出修改为:
//建立一个变量保存Elapsed的值,添加这步只是为了好看
TimeSpan ts = stopwatch.Elapsed;
//额外输出天数的输出
Console.WriteLine("{0:00}:{1:00}:{2:00}:{3:00}.{4:0000}",
ts.Days, ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds);
//其将输出:00:00:00:01.0001左右的值
需要注意的是,在格式化中添加{0:00}
后的00
并不会更改其输出的值,但添加之后输出的数值能更符合日常逻辑
换算值
另外,TimeSpan
类型的属性Elapsed
中还有一些换算属性,这些属性可以将Elapsed
中保存的时间转换成等价的天数、小时数、分钟数、秒数等。
具体如下:
属性名 | 换算结果说明 |
---|---|
TotalDays | 天数 |
TotalHours | 小时数 |
TotalMinutes | 分钟数 |
TotalSeconds | 秒数 |
TotalMilliseconds | 毫秒数 |
- 可以通过调用输出的形式输出这些值
例如可以将代码例中第23行的输出修改为:
TimeSpan ts = stopwatch.Elapsed;
//调用换算值并输出
Console.WriteLine("程序时间换算为:\n" +
"总分钟数:{0}\n" +
"总秒数:{1}\n" +
"总毫秒数:{2}",
ts.TotalMinutes,ts.TotalSeconds,ts.TotalMicroseconds);
其将输出
//程序时间换算为:
//总分钟数:0.016669931666666665
//总秒数:1.0001959
//总毫秒数:1000195.9
Stopwatch类中的Elapsed变体
另外,Stopwatch
类提供了两种Elapsed
属性变体,用来进行不同形式的输出:
具体如下:
属性名 | 类型 | 作用 |
---|---|---|
ElapsedMilliseconds | Long (int64) | 以毫秒为单位保存时间 |
ElapsedTicks | Long (int64) | 以时间戳形式保存时间 |
当Stopwatch
类正在记录时间时,这些属性和Elapsed
属性会同步数据并分别以自己的形式进行保存
改进方案:代码分开计时
代码例
在之前的代码例中,如果需要对文件中的多段代码分开独立计时时,就只能通过多次实例化和start()
,stop()
方法的连用来进行,其将增加代码对内存的需求。
该改进方法可以通过单个实例化的Stopwatch
类来对多个代码分开计时:
using System;
using System.Diagnostics;
class Program
{
static void Main()
{
//代码块1
//使用Stopwatch.StartNew()静态方法,新建Stopwatch实例并开始计时
var TimeRecord1 = Stopwatch.StartNew();
//假设代码运行、暂停计时并输出
//接下来的三个代码块都用同样的结构
System.Threading.Thread.Sleep(1000);
TimeRecord1.Stop();
Console.WriteLine(TimeRecord1.Elapsed.ToString());
//代码块2
//使用Reset()将计时器重置为0
TimeRecord1.Reset();
TimeRecord1.Start();
//假设代码运行、暂停计时并输出
System.Threading.Thread.Sleep(1000);
TimeRecord1.Stop();
Console.WriteLine(TimeRecord1.Elapsed.ToString());
//代码块3
//使用Restart()方法,重置计时器并开始计时
TimeRecord1.Restart();
//假设代码运行、暂停计时并输出
System.Threading.Thread.Sleep(1000);
TimeRecord1.Stop();
Console.WriteLine(TimeRecord1.Elapsed.ToString());
}
}
使用方法
在这个解决方案中,使用的方法如下:
前缀 | 方法名 | 用途 | 说明 |
---|---|---|---|
public static System.Diagnostics.Stopwatch | StartNew() | 创建一个新的Stopwatch实例,并启动计时器 | 可以将返回值赋给Stopwatch或var类型的变量 |
public void | Reset () | 暂停并重启计时器(将值设为0) | |
public void | Restart() | 重启计时器并重新开始计时 |
使用该解决方案时,有两块语法糖:
StartNew()
方法是“实例化后启动计时器”操作的语法糖,当使用该方法时,就完成了实例化并启动计时器的操作Restart()
方法是Reset()
方法的语法糖,因为它在重启计时器的同时也进行了启动操作。
但是也会有两段程序之间有一段不需要进行计时的程序,这段程序存在时,就有可能只能使用Reset()
方法
解决方案2:时间戳计时方式
这种解决方案通过使用计时器的时间戳来进行计时。
代码例
using System;
using System.Diagnostics;
class Program
{
static void Main()
{
//代码块1
//使用GetTimeStamp()静态方法记录时间戳
long TimeRecord1 = Stopwatch.GetTimestamp();
Console.WriteLine("TimeRecord1 is {0}\n", TimeRecord1); //参考用
System.Threading.Thread.Sleep(1000);
//记录第二次时间戳
long TimeRecord2 = Stopwatch.GetTimestamp();
Console.WriteLine("TimeRecord2 is {0}\n", TimeRecord2); //参考用
//输出两次记录的差值,即程序运行时间
TimeSpan ts = Stopwatch.GetElapsedTime(TimeRecord1, TimeRecord2);
Console.WriteLine(ts.ToString());
//代码块2
//记录第三次时间戳
long TimeRecord3 = Stopwatch.GetTimestamp();
Console.WriteLine("TimeRecord3 is {0}\n", TimeRecord3); //参考用
System.Threading.Thread.Sleep(1000);
//输出的简化形式
Console.WriteLine(Stopwatch.GetElapsedTime(TimeRecord3).ToString());
}
}
使用方法
该解决方案使用的方法如下:
前缀 | 方法名 | 用途 |
---|---|---|
public static long | GetTimestamp () | 当被调用时,获取计时器的时间戳 |
public static TimeSpan | GetElapsedTime (long, long) | 计算两个时间戳的差值,并将结果返回给TimeSpan类型变量 |
public static TimeSpan | GetElapsedTime (long) | 上个方法的重载形式,计算带入形参的时间戳和调用该函数时的时间戳的差值,并将结果返回给TimeSpan类型的变量 |
其中“计时器的时间戳”并不是指当前系统时间的时间戳,但确切是什么,该时间戳的计时起始日期也不清楚(求教)。
但是在该解决方案中,只需要知道它代表了当前调用GetTimeStamp()
方法时记录的一个时间确切值,并通过和其他时间戳的求差计算找出程序运行的时间
在使用该解决方案时的运行流程如下:
- 通过在需要找出运行时间的代码前后,通过创建
long
类型的变量,记录两次不同的时间戳 - 在记录时间戳后,通过
GetElapsedTime()
方法,计算两个时间戳的差值,并将其返回TimeSpan
类型的变量。 - 输出记录有时间差值的变量,返回的即是程序运行时间。
另外使用单个参数的GetElapsedTime()
可以通过引入程序运行前的,记录有GerElapsedTime()
返回的时间戳的变量,并计算其和调用该函数时的时间戳计算差值,找出程序运行时间。
使用该解决方案的原因
- 该解决方案使用的方法都是静态方法,无需实例化所在的类即可被调用。
- 使用该解决方案时会减少运行时间(存疑)
参考文献:
Microsoft C#文档:Stopwatch类 及其一级子链接
“贝尔拉梅拉热翔” 的博客文章:C#的TimeSpan介绍