使用Sleep、Timer、Task延时一定时间执行时会受到休眠等影响,比如:10点设置延时1小时后执行,10点半时计算机进入节能休眠状态,休眠持续20分钟,那么以上方法将会在11点20分执行(如果没有休眠影响正常是在11点执行)
解决上面问题可以调用API函数:CreateWaitableTimer、SetWaitableTimer来解决,此方法可设定相对时间或绝对时间来执行,不受中途休眠影响(执行时计算机已经处于工作状态),如果执行的时间点计算还处于休眠状态也可以唤醒计算机(需要电源配置支持),此情况会受到唤醒时间差的影响。
经本人测试,如果是设置了定时,中途时间变动不影响定时时效,比如:定时30分钟后执行,中途时间有变更,依然是30分钟后准时执行,采用SetWaitableTimer设置绝对时间(比如:12:00执行),中途系统时钟调整后,依然是12:00准时执行,相当于相对时间匹配时钟跳动计数(中途系统时间调快调慢,延时一样长),绝对时间匹配系统实时时间(中途系统时间调快了会,延时变短,调慢了,延时变长)。
相对时间 指一个时间差TimeSpan,如:多少分钟,多少秒;
绝对时间 指一个具体时间点DateTime,如:12:00。
下面是本人写的一个定时器类,可以模拟Thread.Sleep()延时一定时间执行且不受电源中途休眠影响,也可以实现定时到指定时间再执行, 同时也可以在其它线程调用CancelSleep()立即取消阻塞。
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WakeUpFromSleep
{
/// <summary>
/// 自定义的定时器(对象实体不建议跨线程使用,后面调用的Sleep会覆盖之前的调用,导致定时混乱)
/// </summary>
internal class MyTimer
{
/// <summary>
/// 创建可等待计时器
/// </summary>
/// <param name="lpTimerAttributes">指定一个结构设置对象的安全特性,传递零值可使用对象的默认安全设置</param>
/// <param name="bManualReset">TRUE,手动重置计时器;FALSE,自动重置计时器</param>
/// <param name="lpTimerName">指定可等待计时器对象的名称(如果已经存在相同名称的计时器则反回原来已经创建的计时器)</param>
/// <returns></returns>
[DllImport("kernel32.dll", SetLastError = true)]
public static extern SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes, bool bManualReset, string lpTimerName);
/// <summary>
/// 启动一个可等待计时器,将它设为未发信号状态
/// </summary>
/// <param name="hTimer">定时器</param>
/// <param name="pDueTime">时间(正数为绝对时间/时间点,负数为相对时间/时间差)</param>
/// <param name="lPeriod">周期间隔(如果是0只触发一次,否则重复触发)</param>
/// <param name="pfnCompletionRoutine">回调函数</param>
/// <param name="lpArgToCompletionRoutine">回调函数的参数</param>
/// <param name="fResume">如果为TRUE,而且系统支持电源管理,那么在计时器触发的时候,系统会退出省电模式。</param>
/// <returns></returns>
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWaitableTimer(SafeWaitHandle hTimer, [In] ref long pDueTime, int lPeriod, IntPtr pfnCompletionRoutine, IntPtr lpArgToCompletionRoutine, bool fResume);
/// <summary>
/// 关闭句柄释放资源
/// </summary>
/// <param name="hObject"></param>
/// <returns></returns>
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(SafeWaitHandle hObject);
/// <summary>
/// 取消计时器(调用后计时器将不会触发,关联Event将无法收到信号,调用WaitOne会一直阻塞)
/// </summary>
/// <param name="hTimer"></param>
/// <returns></returns>
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CancelWaitableTimer(SafeWaitHandle hTimer);
/// <summary>
/// 等待计时器信号
/// </summary>
/// <param name="hTimer">计时器</param>
/// <param name="dwMilliseconds">最大等待时长(毫秒)</param>
/// <returns></returns>
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint WaitForSingleObject(SafeWaitHandle hTimer, uint dwMilliseconds);
EventWaitHandle eventHandle;
SafeWaitHandle waitHandle;
public MyTimer()
{
eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
waitHandle = CreateWaitableTimer(IntPtr.Zero, true, DateTime.Now.Ticks.ToString());
eventHandle.SafeWaitHandle = waitHandle;
}
/// <summary>
/// 将当前线程挂起指定毫秒数(会覆盖之前调用的Sleep或SleepToDatetime)
/// </summary>
/// <param name="milliseconds">毫秒数</param>
public void Sleep(int milliseconds)
{
long duetime = milliseconds;
duetime = duetime * -10000;
if (SetWaitableTimer(waitHandle, ref duetime, 0, IntPtr.Zero, IntPtr.Zero, true))
{
eventHandle.WaitOne();
}
else
{
Thread.Sleep(milliseconds);
}
}
/// <summary>
/// 将当前线程挂起到指定时间(会覆盖之前调用的Sleep或SleepToDatetime)
/// </summary>
/// <param name="dateTime">时间(如果时间值小于当前时间,则立即返回)</param>
public void SleepToDatetime(DateTime dateTime)
{
long duetime = dateTime.ToFileTime();
if (SetWaitableTimer(waitHandle, ref duetime, 0, IntPtr.Zero, IntPtr.Zero, true))
{
eventHandle.WaitOne();
}
else
{
while(DateTime.Now<dateTime)
{
Thread.Sleep(1000);
}
}
}
/// <summary>
/// 取消当前挂起,使Sleep或SleepToDatetime立即返回(多线程使用)
/// </summary>
/// <returns></returns>
public bool CancelSleep()
{
long duetime = 0;
return SetWaitableTimer(waitHandle, ref duetime, 0, IntPtr.Zero, IntPtr.Zero, true);
}
/// <summary>
/// 释放资源(使用完记得要调用)
/// </summary>
public void Dispose()
{
eventHandle.Dispose();
waitHandle.Dispose();
}
/// <summary>
/// 一定时间后唤醒一次计算机(需要在电源配置中启用功能,睡眠-允许使用唤醒定时器-启用)
/// 参考链接:https://www.cnblogs.com/wanglg/p/7509717.html
/// </summary>
/// <param name="second">多少秒后唤醒</param>
/// <exception cref="Win32Exception"></exception>
public static void SetWaitForWakeUpTime(long second)
{
long duetime = second * -10000000;
using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, DateTime.Now.Ticks.ToString()))
{
if (SetWaitableTimer(handle, ref duetime, 0, IntPtr.Zero, IntPtr.Zero, true))
{
using (EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset))
{
wh.SafeWaitHandle = handle;
//Application.SetSuspendState(PowerState.Suspend, true, false);//通知系统进入休眠状态
//wh.WaitOne();//等待定时器激发
}
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
//CloseHandle(handle);//不能在using块中使用,导致重复释放会抛异常
}
}
/// <summary>
/// 将当前线程挂起指定毫秒数
/// 为避免重复创建定时器,建议先创建实例对象再调用,不要使用静态方法
/// </summary>
/// <param name="milliSecond">毫秒</param>
/// <exception cref="Win32Exception"></exception>
public static void Wait(long milliSecond)
{
long duetime = milliSecond * -10000;
using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, DateTime.Now.Ticks.ToString()))
{
if (SetWaitableTimer(handle, ref duetime, 0, IntPtr.Zero, IntPtr.Zero, true))//最后一个参数代表同时唤醒休眠
{
using (EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset))
{
wh.SafeWaitHandle = handle;
wh.WaitOne();//等待定时器激发
}
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
/// <summary>
/// 将当前线程挂起到指定时间
/// 为避免重复创建定时器,建议先创建实例对象再调用,不要使用静态方法
/// </summary>
/// <param name="dateTime">时间(如果时间值小于当前时间,则立即返回)</param>
/// <exception cref="Win32Exception"></exception>
public static void WaitToDatetime(DateTime dateTime)
{
long duetime = dateTime.ToFileTime();
using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, DateTime.Now.Ticks.ToString()))
{
if (SetWaitableTimer(handle, ref duetime, 0, IntPtr.Zero, IntPtr.Zero, true))//最后一个参数代表同时唤醒休眠
{
using (EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset))
{
wh.SafeWaitHandle = handle;
wh.WaitOne();//等待定时器激发
}
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
}
}