Moderne Windows-PCs verbrauchen schon lange nicht mehr so viel Strom wie noch vor einigen Jahren. Dennoch sind in Windows einige Funktionen implementiert, die den Stromverbrauch eines Rechners noch weiter senken sollen. Ein wichtiges Mittel dazu stellt der Energiesparmodus dar, der nach einer vorher definierten Zeitspanne eingenommen wird. D.h. dass der Rechner beispielsweise in den Standby-Modus oder in den Ruhezustand wechselt, wenn dieser eine gewisse Zeit nicht in Verwendung ist. Auf diese Art läuft der Rechner theoretisch nur dann, wenn dieser auch gebraucht wird. Nun gibt es allerdings Szenarien, in den der Rechner zwar nicht aktiv verwendet wird (d.h. keine Benutzereingaben vorliegen), aber trotzdem nicht einfach in den Standby-Modus wechseln darf. Ein Beispiel hierfür wäre das Abspielen einer DVD/BluRay. Während der Film läuft, wird am Rechner üblicherweise nicht gearbeitet, aber in den Standby-Modus soll der PC auch nicht wechseln, da ansonsten die Wiedergabe des Film unterbrochen werden würde.
Es gibt zwei Funktionen, um den Standby-Modus in eigenen Programmen zu unterdrücken: SetThreadExecutionState und Power Availability Requests. Beide Funktionen sind dabei nicht in das .NET Framework integriert, sondern Teil des Windows APIs. Um mit Sprachen wie C# darauf zugreifen zu können, sind dazu die Platform Invocation Services (P/Invoke) notwendig.
Alle Code-Beispiele des Artikels sind in C# aufgeführt, allerdings ist die Vorgehensweise auch auf andere .NET Sprachen übertragbar. Die Visual Studio Solution mit allen Code-Beispielen steht weiter unten zum Download bereit.
SetThreadExecutionState
Ab Windwos XP/Windows Server 2003 gibt es die Funktion SetThreadExecutionState im Modul kernel32.dll. Um die Funktion in C# verwenden zu können, sind folgende Definitionen notwendig:
private const uint ES_SYSTEM_REQUIRED = 0x00000001; private const uint ES_DISPLAY_REQUIRED = 0x00000002; private const uint ES_USER_PRESENT = 0x00000004; // Only supported by Windows XP/Windows Server 2003 private const uint ES_AWAYMODE_REQUIRED = 0x00000040; // Not supported by Windows XP/Windows Server 2003 private const uint ES_CONTINUOUS = 0x80000000; [DllImport("kernel32.dll", SetLastError = true)] internal static extern uint SetThreadExecutionState(uint esFlags);
Der Standby-Modus kann nun folgendermaßen unterdrückt werden:
private static void SuppressStandby() { var success = SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED); if (success == 0) { // Failed to suppress standby var error = Marshal.GetLastWin32Error(); if (error != 0) throw new Win32Exception(error); } }
Zu beachten ist hier das Argument, welches an die Funktion SetThreadExecutionState übergeben wird. Durch diese wird die aktuelle Verwendung des Rechners genauer spezifiziert. Folgende Angaben sind hier möglich und werden im Stil von Bitflags übergeben. In diesem Zusammenhang geben System-Idle-Timer bzw. Bildschirm-Idle-Timer die Werte an, nach welcher Zeit der Inaktivität das System in den Standby-Modus wechseln bzw. der Bildschirm in den Energiespar-Modus versetzt werden soll. Beide Werte können getrennt voneinander in den Windows Energieoptionen konfiguriert werden.
- ES_SYSTEM_REQUIRED: Der Standby Modus wird unterdrückt und der System-Idle-Timer wird zurückgesetzt.
- ES_DISPLAY_REQUIRED: Durch Zurücksetzen des Bildschirm-Idle-Timers wird verhindert, dass der Bildschirm in den Energiespar-Modus versetzt wird. Wenn sich der Bildschirm bereits im Energiespar-Modus befindet, wird dieser wieder in den Normal-Zustand versetzt. Letzteres ist unter Windows 8 nicht mehr möglich.
- ES_USER_PRESENT: Unter Windows XP und Windows Server 2003 muss dieser Wert zusammen mit ES_CONTINUOUS verwendet werden und gibt an, dass ein Benutzer weiterhin aktiv ist. Ebenso werden der Bildschirm- als auch der System-Idle-Timer zurück gesetzt. Auf anderen Betriebssystem-Versionen wird dieser Wert nicht mehr unterstützt und der Aufruf von SetThreadExecutionState wird scheitern, wenn dieser Wert zusammen mit einem anderen Flag übergeben wird.
- ES_AWAYMODE_REQUIRED: Dieser Wert muss zusammen mit ES_CONTINUOUS angegeben werden und aktiviert den sog. Away-Mode. Der Away-Mode gibt an, dass das System scheinbar nicht mehr in Verwendung ist, aber trotzdem noch benötigt wird. Dies ist z.B. bei Multimedia-Anwendungen oder Software zum Brennen von DVDs/BluRays notwendig.
Wenn der Benutzer nun das System (manuell) in den Standby versetzt, wird lediglich der Ton deaktiviert und der Bildschirm ausgeschaltret. Der Computer ist dann nur scheinbar im Standby (genauer gesagt im Status ACPI S0), läuft tatsächlich aber weiterhin.
Unter Windows XP und Windows Server 2003 wird dieser Wert nicht unterstützt. - ES_CONTINUOUS: Wenn dieser Wert zusammen mit einem weiteren Flag übergeben wird, wird der spezifizierte Zustand beibehalten bis ein weiterer Aufruf von SetThreadExecutionState mit ES_CONTINUOUS stattfindet.
Im Normalfall wird daher immer ES_CONTINUOUS in Verbindung mit einem anderen Flag verwendet (z.B. ES_CONTINUOUS | ES_SYSTEM_REQUIRED). Falls das ES_CONTINUOUS-Flag nicht mit angegeben wird, wird meist immer nur der System- und/oder der Bildschirm-Idle-Timer zurück gesetzt. Dies ist in dem meisten Fällen nicht ausreichend, da dadurch kein dauerhaftes Unterdrücken des Standby-Modus erzielt wird.
Wird SetThreadExecutionState nur mit ES_CONTINUOUS aufgerufen, kehrt Windows wieder in den Normalzustand zurück und geht nach den in der Systemsteuerung angegebenen Zeiten in den Standby-Modus:
private static void EnableStandby() { var success = SetThreadExecutionState(ES_CONTINUOUS); if (success == 0) { // Failed to enable standby var error = Marshal.GetLastWin32Error(); if (error != 0) throw new Win32Exception(error); } }
Wenn der Standby-Modus mittels SetThreadExecutionState unterdrückt wurde, kann dies ab Windows 7/Windows Server 2008 R2 mit Hilfe des Tools powercfg überprüft werden. Dazu muss lediglich powercfg -requests auf der Kommandozeile eingegeben werden. Das Tool gibt dann folgende Informationen aus:
DISPLAY: Keine. SYSTEM: [PROCESS] \Device\HarddiskVolume1\Projects\SuppressStandbySamples\SetThreadExecutionState\bin\Debug\SetThreadExecutionState.exe AWAYMODE: Keine.
Hieraus wird ersichtlich, dass der Standby-Modus auf Grund des Programms SetThreadExecutionState.exe unterdrückt wurde. Mehr Informationen (wie z.B. der Grund der Standby-Unterdrückung) können bei der Verwendung von SetThreadExecutionState nicht mitgegeben werden.
Power Availability Requests
Die wesentlich modernere Methode zur Unterdrückung des Standby-Modus stellen die Power Availability Requests dar. Diese sind ebenfalls im Modul kernel32.dll implementiert und müssen per P/Invoke eingebunden werden:
private const int POWER_REQUEST_CONTEXT_VERSION = 0; private const int POWER_REQUEST_CONTEXT_SIMPLE_STRING = 0x1; [DllImport("kernel32.dll", SetLastError = true)] internal static extern IntPtr PowerCreateRequest(ref POWER_REQUEST_CONTEXT Context); [DllImport("kernel32.dll", SetLastError = true)] internal static extern bool PowerSetRequest(IntPtr PowerRequestHandle, PowerRequestType RequestType); [DllImport("kernel32.dll", SetLastError = true)] internal static extern bool PowerClearRequest(IntPtr PowerRequestHandle, PowerRequestType RequestType); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] internal struct POWER_REQUEST_CONTEXT { public UInt32 Version; public UInt32 Flags; [MarshalAs(UnmanagedType.LPWStr)] public string SimpleReasonString; } internal enum PowerRequestType { PowerRequestDisplayRequired = 0, // Not to be used by drivers PowerRequestSystemRequired, PowerRequestAwayModeRequired, // Not to be used by drivers PowerRequestExecutionRequired // Not to be used by drivers }
Nach der Einbindung der Funktionen mittels P/Invoke und Definition der benötigten Datentypen kann der Standby-Modus auf folgende Art und Weise unterdrückt werden (die Referenz auf einen aktuell gesetzen Power Request wird dabei in der Variablen currentPowerRequest gespeichert):
private static void SuppressStandby() { // Clear current power request if there is any. if (currentPowerRequest != IntPtr.Zero) { PowerClearRequest(currentPowerRequest, PowerRequestType.PowerRequestSystemRequired); currentPowerRequest = IntPtr.Zero; } // Create new power request. POWER_REQUEST_CONTEXT pContext; pContext.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING; pContext.Version = POWER_REQUEST_CONTEXT_VERSION; pContext.SimpleReasonString = "Standby suppressed by PowerAvailabilityRequests.exe"; currentPowerRequest = PowerCreateRequest(ref pContext); if (currentPowerRequest == IntPtr.Zero) { // Failed to create power request. var error = Marshal.GetLastWin32Error(); if (error != 0) throw new Win32Exception(error); } bool success = PowerSetRequest(currentPowerRequest, PowerRequestType.PowerRequestSystemRequired); if (!success) { // Failed to set power request. currentPowerRequest = IntPtr.Zero; var error = Marshal.GetLastWin32Error(); if (error != 0) throw new Win32Exception(error); } }
Soweit vorhanden wird zunächst ein bereits vorhandener Power Request entfernt. Anschließend wird ein Power Request Context erzeugt und mit der Methode PowerSetRequest aktiviert. Mit Hilfe des Enums PowerRequestType wird der Power Request genauer spezifiziert:
- PowerRequestDisplayRequired: Gibt an, dass der Bildschirm nicht in den Energiespar-Modus versetzt werden soll. Wenn sich der Bildschirm bereits im Energiespar-Modus befindet, wird dieser reaktiviert.
- PowerRequestSystemRequired: Verhindert, dass der Computer nach einer gewissen Zeit in den Standby-Modus wechselt.
- PowerRequestAwayModeRequired: Aktiviert den sog. Away-Mode. Dieser gibt an, dass das System scheinbar nicht mehr in Verwendung ist, aber trotzdem noch benötigt wird. Dies ist z.B. bei Multimedia-Anwendungen oder Software zum Brennen von DVDs/BluRays notwendig.
Wenn der Benutzer nun das System (manuell) in den Standby versetzt, wird lediglich der Ton deaktiviert und der Bildschirm ausgeschaltret. Der Computer ist dann nur scheinbar im Standby (genauer gesagt im Status ACPI S0), läuft tatsächlich aber weiterhin. - PowerRequestExecutionRequired: Wenn dieser Status gesetzt wurde, wird der aufrufende Prozess weiterhinausgeführt und wird nicht vom Process Lifetime Management (PLM) beendet oder ausgesetzt. Wie lange der Prozess tatsächlich ausgeführt wird, hängt dabei vom Betriebssystem und den aktuellen Energiespar-Optionen ab. Dieser Status wird erst ab Windows 8 unterstützt und ist eher für Windows-Apps interessant.
Bis auf PowerRequestSystemRequired kann kein anderer Status von einem Treiber gesetzt werden (siehe Power Availability Requests im Kernel-Mode).
Um das System wieder in den Normalzustand zu versetzten, ist folgender Code nötig:
private static void EnableStandby() { // Only try to clear power request if any power request is set. if (currentPowerRequest != IntPtr.Zero) { var success = PowerClearRequest(currentPowerRequest, PowerRequestType.PowerRequestSystemRequired); if (!success) { // Failed to clear power request. currentPowerRequest = IntPtr.Zero; var error = Marshal.GetLastWin32Error(); if (error != 0) throw new Win32Exception(error); } else { currentPowerRequest = IntPtr.Zero; } } }
Hier wird einfach die Methode ClearPowerRequest mit der Referenz zum aktiven Power Request aufgerufen. Da jeder Power Request eigenständig verwaltet wird, kann ein Programm auch mehrere Power Requests zeitgleich aktivieren. Hier ist jedoch zu beachten, dass ein Programm jeden aktivierten Power Request auch selbstständig wieder deaktivieren muss (PowerClearRequest).
Wenn das Programm nun ausgeführt wird, liefert powercfg -requests nun eine Ausgabe mit einer Beschreibung, warum der Standby-Modus momentan unterdrückt wird:
DISPLAY: Keine. SYSTEM: [PROCESS] \Device\HarddiskVolume1\Projects\SuppressStandbySamples\PowerAvailabilityRequests\bin\Debug\PowerAvailabilityRequests.exe Standby suppressed by PowerAvailabilityRequests.exe AWAYMODE: Keine.
Detaillierte Informationen und Power Availability Requests im Kernel-Mode
Im vorherigen Beispiel wurde lediglich ein einfacher String als Begründung für das Unterdrücken des Standby-Modus angegeben. Power Availability Requests unterstützen jedoch auch die Angabe von detaillierten Informationen zur Standby-Unterdrückung. Dies ist insbesondere zur Angabe von lokalisieren Informationen zur Standby-Unterdrückung in unmanaged Umgebungen (C++) interessant. Da in managed Umgebungen (.NET) jedoch anders mit lokalisierten Resourcen umgegangen wird (im Allgemeinen kommen hier sog. Satelliten-Assemblys zum Einsatz), ist dieser Ansatz in managed Umgebungen meist nicht notwendig.
Ebenso erzeugt die normale Vorgehensweise zum Setzen von Power Requests solche im User-Mode, also auf einer eingeschränkten Priviligierungsebene. Es sind allerdings auch Funktionen verfügbar, die Power Requests im Kernel-Modus, also auf der Betriebssystemebene, setzen. Diese Funktionen sind zur Programmierung von (hardware-nahen) Treibern gedacht, die erhöhte Privilegien benötigen. In diesem Fall kann allerdings nur PowerRequestType.PowerRequestSystemRequired genutzt werden, alle anderen PowerRequestTypes sind in diesem Kontext nicht möglich. Da eine genauere Beschreibung jedoch den Rahmen des Artikels sprengen würde, wird an dieser Stelle lediglich auf das offizielle Microsoft-Dokument über Power Availability Requests verwiesen, in der auch eine Beschreibung der Power Availability Requests im Kernel-Mode enthalten ist.
SetThreadExecutionState und Power Availability Requests im direkten Vergleich
Wenn man die beiden Methoden SetThreadExecutionState und Power Availability Requests gegenüberstellt, ergeben sich folgende Unterschiede:
SetThreadExecutionState | Power Availability Requests | |
---|---|---|
Setzen und Aufheben der Standby-Unterdrückung | im gleichen Thread der Anwendung | auch in unterschiedlichen Threads der Anwendung |
Angabe eines Grundes möglich | nein | ja |
Mehrfache Anwendung | nein | ja |
Verfügbar ab Windows-Version | Windows XP/Windows Server 2003 | Windows 7/Windows Server 2008 R2 |
Verügbarkeit von Power Availability Requests prüfen
Power Availability Requests sind, wie bereits erwähnt, erst ab Windows 7/Windows Server 2008 R2 verfügbar. Wenn man sich hierbei nicht ausschließlich auf die Betriebssystem-Version verlassen möchte, kann man die Verfügbarkeit auch direkt über GetProcAddress ermitteln:
[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)] internal static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("kernel32.dll", CharSet=CharSet.Unicode)] internal static extern IntPtr LoadLibrary(string dllToLoad); private static bool PowerAvailabilityRequestsSupported() { var ptr = LoadLibrary("kernel32.dll"); var ptr2 = GetProcAddress(ptr, "PowerSetRequest"); if (ptr2 == IntPtr.Zero) { // Power availability requests NOT suppoted. return false; } else { // Power availability requests ARE suppoted. return true; } }
Best Practices
Zum Unterdrücken des Standby-Modus sollten im Allgemeinen folgende Dinge beachtet werden:
- Nach Möglichkeit sollten Power Availability Requests verwendet werden, da dies einfach die modernere und flexiblere Methode darstellt, den Standby-Modus zu unterdrücken. Wenn das Programm allerdings auch ältere Windows-Versionen unterstützen soll, kann durch die o.g. Prüfung auf Verfügbarkeit von Power Availability Requests einfach ermittelt werden, ob diese auf der aktuellen Platform verwendet werden können. Es bietet sich dann folgende Fallunterscheidung an: Wenn Power Availability Requests verfügbar sind, sollten diese auch verwendet werden. Falls nicht, kann man als Fallback-Strategie auf SetThreadExecutionState zurück greifen.
- Der Standby-Modus sollte nur so lange wie unbedingt nötig unterdrückt werden. D.h. wenn ein Szenario die Unterdrückung des Standby-Modus erfordert (z.B. ein Download gestartet wird), sollte diese Unterdrückung nach Beendigung des Szenarios wieder unverzüglich aufgehoben werden (z.B. wenn der Download fertig gestellt wurde).
Windows beinhaltet nicht umsonst ein fortschrittliches System zum Energiesparen. Dies sollte nicht durch die unüberlegte Verwendung der o.g. Funktionen ausgehebelt werden. - Es sollte auch sicher gestellt sein, dass im Fehlerfall immer eine evtl. vorhandene Standby-Unterdrückung wieder deaktiviert wird.
Download
Hier gibt es die komplette Visual Studio Solution mit dem kompletten Code dieses Beitrags zum Download:
Download SuppressStandbySamples.zip