Thursday, September 10, 2009

Memory leak issue in “How Do I: Programmatically Monitor for a Specific Time of Day Without Draining a Device Battery?”

Recently I worked on improving our SmartOrganizer 3.1 application. I need to add notification to it, so when a task or appointment is due, the application will prompt the user. Also I want this to run efficiently so it won’t drain the device battery. I found some sample code from “How Do I” video for Device.

Though the code worked well initially, I found a couple issues which caused memory leak later. Here I will post the original code and my fix.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using HowDoI.Examples;
using System.Threading;

namespace TimeOfDayEvent_CS
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

const string _eventName = @"TimeOfDayEventExample";
const string _timeOfDayEventName = @"\\.\Notifications\NamedEvents\" + _eventName;
IntPtr _nativeWaitHandle = IntPtr.Zero;
Thread _backgroundThread = null;
bool _programIsShuttingDown = false;
private void menuSetTimeNtfy_Click(object sender, EventArgs e)
{
// Declare and convert time values
DateTime targetTime = GetTargetTime();
long targetTimeAsFileTimeUTC = targetTime.ToFileTime();
long targetTimeAsFileTimeLocal = 0;
Win32.FileTimeToLocalFileTime(ref targetTimeAsFileTimeUTC,
ref targetTimeAsFileTimeLocal);
SystemTime targetTimeAsSystemTime = new SystemTime();
Win32.FileTimeToSystemTime(ref targetTimeAsFileTimeLocal, targetTimeAsSystemTime);

// Create named Win32 Event Object
_nativeWaitHandle = Win32.CreateEvent(0, 0, 0, _eventName);

// Start the background thread
_backgroundThread = new Thread(ThreadFunction);
_backgroundThread.Start();

Win32.CeRunAppAtTime(_timeOfDayEventName, targetTimeAsSystemTime);
}

void ThreadFunction()
{
// Wait for the event to signal
// When signaled our target time has happened
Win32.WaitForSingleObject(_nativeWaitHandle, -1);

// Do our Target Time processing
if (!_programIsShuttingDown)
BeginInvoke((TimeHasOccurredDelegate)TimeHasOccurred, new object[] { DateTime.Now });
//TimeHasOccurred(DateTime.Now);
}

void TimeHasOccurred(DateTime time)
{
notification1.Text =
string.Format("
<html><body><font color=\'#000000\'><b>It's Time: {0}<b></font></body></html>",
time.ToString());
notification1.Visible = true;
Debug.WriteLine("
It's Time: " + time.ToString());
}

delegate void TimeHasOccurredDelegate(DateTime time);

private DateTime GetTargetTime()
{
//DateTime targetTime = new DateTime(2007, 8, 31, 10, 15, 0);
// For demo purposes pick date time 1 minute in the future
DateTime currentTime = DateTime.Now;
DateTime targetTime = currentTime + new TimeSpan(0, 1, 0);

Debug.WriteLine("
CurrentTime: " + currentTime.ToString());
Debug.WriteLine("
TargetTime: " + targetTime.ToString());

return targetTime;
}

const int _threadShutdownTimeout = 30000;
private void Form1_Closing(object sender, CancelEventArgs e)
{
if (_nativeWaitHandle != IntPtr.Zero)
{
_programIsShuttingDown = true;
Win32.SetEvent(_nativeWaitHandle);
if (_backgroundThread != null)
{
bool shutdownSucceeded = _backgroundThread.Join(_threadShutdownTimeout);
if (!shutdownSucceeded)
_backgroundThread.Abort();
}
}
}
}
}











Here is the native method calls




using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace HowDoI.Examples
{
static class Win32
{
#region Win32 Time-related Functiions

[DllImport("CoreDLL.dll")]
public static extern int CeRunAppAtTime(string application, SystemTime startTime);
[DllImport("CoreDLL.dll")]
public static extern int FileTimeToSystemTime(ref long lpFileTime, SystemTime lpSystemTime);
[DllImport("CoreDLL.dll")]
public static extern int FileTimeToLocalFileTime(ref long lpFileTime, ref long lpLocalFileTime);
[DllImport("CoreDLL.dll")]
public static extern int ShowWindow(IntPtr hWnd, int nCmdShow);

#endregion

#region Win32 Event Object Functions

[DllImport("CoreDLL.dll")]
public static extern IntPtr CreateEvent(int alwaysZero, int manualReset, int initialState, string eventName);
[DllImport("CoreDLL.dll")]
public static extern int WaitForSingleObject(IntPtr handle, int waitTimeInMilliseconds);
[DllImport("CoreDLL.dll")]
private static extern int EventModify(IntPtr handle, int eventAction);
[DllImport("CoreDLL.dll")]
public static extern int CloseHandle(IntPtr handle);

public static int SetEvent(IntPtr handle)
{
const int EVENT_SET = 3;
return EventModify(handle, EVENT_SET); // in WM, SetEvent, ResetEvent, & PulseEvent are all implemented as EventModify
}

#endregion

}

#region Win32 SystemTime

[StructLayout(LayoutKind.Sequential)]
public class SystemTime
{
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}

#endregion

}







The time monitoring and notification in above code worked perfectly. However, I noticed that if I stop the application before the time is up, the application disappeared from the task manager, but the memory was not released. Furthermore, I used the Remote Process Viewer to check which process is running. I can see that process is still there. As I was writing this blog, I noticed that it only happen on device, not emulator. For some reasons, the device couldn’t kill the background thread when it is not done. The solution is. in fact, pretty simple - reset the timer. Add the following line of code before Join the background thread. [currentTime] is current time in SystemTime format.




Win32.CeRunAppAtTime(_timeOfDayEventName, [currentTime]);
bool shutdownSucceeded = _backgroundThread.Join(_threadShutdownTimeout);



No comments:

Post a Comment