Click here to Skip to main content
Click here to Skip to main content

Use System Events To Protect Your Application Data

, 12 Nov 2004
Rate this:
Please Sign up or sign in to vote.
Handle System Events to protect your application data.

Introduction

If you’ve used the new MS Office products recently, you’ve noticed that Outlook, for instance, does not let you log off unless the application is closed. This is because Outlook does its data finalization when it closes and does not want cached data to be corrupted by a user logging off.

I recently worked on an application where we had to log streaming data which was cached for performance reasons, and then dumped the data to a file after certain criteria was met. The importance of the data mandated that we protect the collected data at all costs. It then struck me that it would be necessary to prevent the user from closing the application or shutting down the system while volatile data was being processed and resident in the cache.

The sample program presented here is not anywhere close to being a real production application, rather a simple program coded for the sole purpose of illustrating the concepts in this article. It presents a simple application that logs some random data (in a separate thread) and periodically writes cached data to a log file. If the user tries to log off, the application prompts the user with a choice to:

  • Save data and log off.
  • Don't save data and log off.
  • Cancel the user's logout or shutdown request, and continue.

About System Events

SystemEvents are events raised by, well…, the system. These are events that are raised in response to actions by the user that affect the operating environment. SystemEvents are not to be confused with Win32 system events that were kernel level events accessible to all programs. The events we are referring to here are those raised by the SystemEvents class in the Microsoft.Win32 namespace.

Events raised by the SystemEvents class are as follows:

  • DisplaySettingsChanged

    Occurs when the user changes the display settings.

  • EventsThreadShutdown

    Occurs before the thread that listens for system events is terminated. Delegates will be invoked on the events thread.

  • InstalledFontsChanged

    Occurs when the user adds fonts to or removes fonts from the system.

  • LowMemory

    Occurs when the system is running out of available RAM.

  • PaletteChanged

    Occurs when the user switches to an application that uses a different palette.

  • PowerModeChanged

    Occurs when the user suspends or resumes the system.

  • SessionEnded

    Occurs when the user is logging off or shutting down the system.

  • SessionEnding

    Occurs when the user is trying to log off or shutdown the system.

  • TimeChanged

    Occurs when the user changes the time on the system clock.

  • TimerElapsed

    Occurs when a Windows timer interval has expired.

  • UserPreferenceChanged

    Occurs when a user preference has changed.

  • UserPreferenceChanging

    Occurs when a user preference is changing.

Of the provided system events, the following are particularly useful to our application:

  • SessionEnding – Want to stop user from closing app with cached data.
  • LowMemory – Want to write cached data in hopes of reducing working set.
  • PowerModeChanged – Write cached data before going into standby mode.

In addition to these system events, we also want to trap the ApplicationExit event of the application we are running to make sure we clean up even when the user closes the window.

The Sample Program

Sample screenshot

Registering for events:

In the main form’s Load event handler, we register for the desired events. Here we make sure that we also handle the Close event for the form in addition to the system events to make sure the cached data gets written to the file.

private void Form1_Load(object sender, System.EventArgs e) {
  Application.ApplicationExit +=new EventHandler(Application_ApplicationExit);

  /* Register for system events to detect user trying 
     to log off or low memory condition */
  SystemEvents.SessionEnding +=new 
        SessionEndingEventHandler(SystemEvents_SessionEnding);
  SystemEvents.LowMemory +=new EventHandler(SystemEvents_LowMemory);
  SystemEvents.PowerModeChanged += new 
        PowerModeChangedEventHandler(SystemEvents_PowerModeChanged);

  /* can't stop what hasn't been started */
  button2.Enabled = false;

  /* The local log file */
  this.fileName = 
   Environment.GetFolderPath( Environment.SpecialFolder.LocalApplicationData ) 
   + "\\DumLogFile.bin";
  this.label1.Text = this.fileName;
}

When handling the SessionEnding event, we prompt the user with three choices. If the user chooses to cancel the logout, we simply set the Cancel flag of the SessionEndingEventArgs argument to false to cancel the logout. If the user chooses to logout, then we decide whether to write our cached data to the log or not and let the logout proceed.

private void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e) {
  /* Don't care if user logs out while no logging going on */
  if( logThread == null ) return;
  /* User is trying to log out. Prompt the user with choices */
  DialogResult dr = MessageBox.Show( "Critical Data In Cache!\n"+
    "Click Yes to save data and log out\n"+ 
    "Click No to logout without saving data\n"+ 
    "Click Cancel to cancel logout and manually stop the application", 
    "Data Logging In Progress", 
    MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation );

  /* User promises to be good and manually stop the app from now on(yeah right) */
  /* Cancel the logout request, app continues */
  if( dr == DialogResult.Cancel ){
    e.Cancel = true;
  }

  /* Good user! Santa will bring lots of data this year */
  /* Save data and logout */
  else if( dr == DialogResult.Yes ){
    /* Write data and tell the event handler to not cancel the logout */
    this.WriteCacheToFile();
    e.Cancel = false;
  }
  /* Bad user! doesn't care about poor data */
  else if( dr == DialogResult.No ){
    e.Cancel = false;
    return;
  } 
}

In a real world application, the data cache might actually be fairly large if disk IO is a latency concern. Hence, we handle the LowMemory event as well. To handle this event, we simply write our cached data to the disk, in hopes of alleviating the burden on the process working set and therefore the system RAM.

private void SystemEvents_LowMemory(object sender, EventArgs e) {
  /* Don't care if user logs out while no logging going on */
  if( logThread == null ) return; 
  /* System is low on memory, write to file. */
  this.WriteCacheToFile(); 
}

Another concern to an application developer is that of the system in standby or suspend mode. In this mode, the system state is saved and the computer is placed in a power save mode. This is IMHO the most dangerous scenario, for people often forget whether the system is in standby or off. This poses a potential for unsaved data to be lost. To handle this case, we handle the PowerModeChanged event. This event provides information whether the power mode is being resumed, suspended, or simply changed (as in laptop battery etc.). Since this event provides no means for cancellation, we simply write out our cache data in case the system should fail to recover from standby properly.

private void SystemEvents_PowerModeChanged(object sender, 
                        PowerModeChangedEventArgs e) {
  /* User is putting the system into standby */
  /* Cannot cancel the operation, write cached data */
  if( e.Mode == PowerModes.Suspend ){
    this.WriteCacheToFile();
  } 
}

The final concern is that the user closes the application while it's running. Though this has been handled by many in the Form's Closing event, it has been highly unreliable and problematic. I hence use the ApplicationExit event of the application class that signals that the application is about to terminate. (See Notes below.)

private void Application_ApplicationExit(object sender, EventArgs e) {
  /* Application is about to exit, cleanup and write data */
  this.Cleanup(); 
}

Note 1:

When handling the SessionEnding event, though the user is prompted with a choice, there is a time limit. The operating system will give the application time to die or kill it forcefully. The example presented here was just to suggest the options available. However, in a real world application, one would most likely cancel the logout, or write data and proceed without giving the spoiled user a clue.

Note 2:

Windows XP supports multiple users logged into the same machine. In this scenario, user A can switch users while programs are still running. The SessionEnding event is not fired when a user remains logged on and temporarily switches to another user. This is important! If user A switches to user B and then user B shuts the system down, user A will not have the ability to react to any message boxes running in user A's process space.

Note 3:

There is nothing a process can do to prevent itself from being killed by the task manager or lower level API. This signal is low level and cannot be handled by any .NET mechanism. When you think about it, this makes sense, though it is a power struggle between the developer and the OS.

Note 4:

I tried looking into the exact purpose of the EventsThreadShutdown event. It would seem to me that it was implemented to prevent the application from terminating a thread waiting for the SessionEnding event. I tried to get this event to fire but couldn’t. I also could not locate any documentation on MSDN or otherwise that would provide more info. If anyone has too much spare time to figure this out, I'd love feedback on this issue.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Neil Baliga
President Verifide Technologies, Inc.
United States United States
Neil Baliga is the founder of Verifide Technologies, Inc. (www.verifide.com), an initiative for automated test systems for product verification used in manufacturing. He strongly believes that the value in software is in its simplicity. His experience includes UNIX, Win32 API, TCP/IP multithreaded servers, C#, C++ et. al, and Radio Frequency (RF) measurement science. He came across .NET in 2001 and has been in love with it ever since. He is an avid LA Lakers and Denver Broncos fan and loves to hang out with his dog 'Reboot'. He is extremely lucky to have the love and support of his beautiful wife Jyothi.

Comments and Discussions

 
QuestionSessionEnded works on shutdown, but doesn't work on logoff Pinmemberlnkinprk20-Jun-11 21:42 
GeneralDosen't work when your main window is hidden PinmemberAsela Gunawardena7-Dec-05 17:48 
GeneralRe: Dosen't work when your main window is hidden Pinmembermashazia3-Apr-06 23:19 
GeneralCongrats for your article Pinmembercutovoi12-Jul-05 8:28 
GeneralPoweMode Resume PinsussAnonymous21-Apr-05 15:05 
GeneralRe: Using System.SessionEnding Event PinmembertomasOBraonain8-Dec-04 4:22 
GeneralRe: Using System.SessionEnding Event PinmemberNeil Baliga8-Dec-04 10:32 
GeneralRe: Using System.SessionEnding Event PinmembertomasOBraonain10-Dec-04 5:31 
GeneralFunny name for a dog... PinsitebuilderUwe Keim13-Nov-04 4:55 
GeneralRe: Funny name for a dog... PinmemberNeil Baliga13-Nov-04 10:18 
GeneralRe: Funny name for a dog... PinmemberRytis Ilciukas4-Jul-07 21:22 
GeneralRe: Funny name for a dog... Pinmemberkrisby17-Nov-04 3:58 
GeneralRe: Funny name for a dog... PinsussAnonymous19-Nov-04 9:42 
GeneralRe: Funny name for a dog... PinmemberBob Aman19-Nov-04 10:03 
GeneralRe: Funny name for a dog... PinmemberBob Aman19-Nov-04 9:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140709.1 | Last Updated 13 Nov 2004
Article Copyright 2004 by Neil Baliga
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid