在现代计算机系统中,经常需要在特定时间唤醒电脑以执行某些任务。本文将介绍如何使用C#编程语言创建一个定时器,该定时器能够在指定的时间唤醒电脑。
电脑通常无法自行启动,但可以在待机或休眠状态下恢复。本文介绍的代码主要围绕两个系统定时器函数:
以下是这两个函数的声明:
[DllImport("kernel32.dll")]
public static extern SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes, bool bManualReset, string lpTimerName);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWaitableTimer(SafeWaitHandle hTimer, ref long pDueTime, int lPeriod, IntPtr pfnCompletionRoutine, IntPtr lpArgToCompletionRoutine, bool fResume);
需要提供一个日期和时间,但是SetWaitableTimer要求一个长整型值。将使用DateTime.ToFileTime()函数将日期/时间从托管转换为系统表示。
以下是程序的核心部分,调用两个API函数:
long waketime = (long)e.Argument;
using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, this.GetType().Assembly.GetName().Name.ToString() + "Timer"))
{
if (SetWaitableTimer(handle, ref waketime, 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());
}
}
在这段代码中,可以看到"e.Argument",这是因为以下代码块会暂停线程执行,直到定时器达到"唤醒时间"值:
using (EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset))
{
wh.SafeWaitHandle = handle;
wh.WaitOne();
}
为了避免阻塞UI线程,需要将这段代码放入一个单独的线程中,由BackgroundWorker对象控制,将唤醒时间作为参数传递给它。
为了便于重用,创建了一个类,并提供了一个事件"Woken"。当后台线程退出时,该事件会被触发:
public event EventHandler Woken;
void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (Woken != null)
{
Woken(this, new EventArgs());
}
}
以下是完整的类定义:
using System;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Threading;
namespace WakeUPTimer
{
class WakeUP
{
[DllImport("kernel32.dll")]
public static extern SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes, bool bManualReset, string lpTimerName);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWaitableTimer(SafeWaitHandle hTimer, ref long pDueTime, int lPeriod, IntPtr pfnCompletionRoutine, IntPtr lpArgToCompletionRoutine, bool fResume);
public event EventHandler Woken;
private BackgroundWorker bgWorker = new BackgroundWorker();
public WakeUP()
{
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);
}
public void SetWakeUpTime(DateTime time)
{
bgWorker.RunWorkerAsync(time.ToFileTime());
}
void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (Woken != null)
{
Woken(this, new EventArgs());
}
}
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
long waketime = (long)e.Argument;
using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, this.GetType().Assembly.GetName().Name.ToString() + "Timer"))
{
if (SetWaitableTimer(handle, ref waketime, 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());
}
}
}
}
}
如果在表单上有一个按钮和一个日期时间选择器,可以这样编写代码:
private void button1_Click(object sender, EventArgs e)
{
WakeUP wup = new WakeUP();
wup.Woken += WakeUP_Woken;
wup.SetWakeUpTime(dateTimePicker1.Value);
}
private void WakeUP_Woken(object sender, EventArgs e)
{
// Do something
}
要使系统进入休眠状态:
Application.SetSuspendState(PowerState.Suspend, false, false);
重要提示:最后一个参数disableWakeEvent需要设置为false。