So I have been working on a program lately, or actually was working on it and came back to check on it. The program is being developed for someone else, who unfortunately has not been cooperative in giving me the stuff I need to test it. The program is used to read in a webpage, parse its data, and then notify if changes have occurred.
Now this program involves using Threads, something that while I understand the concept of, and have gone over, I have never really implemented into a program like this (I have used BackgroundWorkers before but this is not the same).
Well from what I can tell the program actually works as intended. While I am unable to get the site needed to test it, I have been giving it other sites to give me general results. The program I know reads in results and does indeed try to update the form (it yelled at me saying it couldn't add rows until it had columns ... hazaa I got this far).
I decided to test the code today, as I mentioned before I hadn't worked on it in awhile, and everything seems fine (again like I said before). I also ran task manager and monitored the resources of the program, and here lies the problem ... I think I have a memory leak. Let me show you the code and I'll explain more after that.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace ScoreTableDetector2_1v1
{
public delegate void Return_CallBack(string downloadedData);
public delegate void Return_FormData(List<teamDataItem> tempData, string rtbData);
public delegate void SetRTBCallback(string text);
public delegate void SetDGV_New_Callback(teamDataItem tempEntry);
public delegate void SetDGV_Edit_Callback(teamDataItem tempEntry);
//===================================================================================================================
public partial class Form1 : Form
{
//!!!! - NEED to dispose of the thread and its resources when it's finished, primarily speaking of the delegate called threads for the form and parsing data.
private Object thisLock = new Object();
private Object lockFormUpdate = new Object();
private Thread updateForm_Thread = null;
private Thread updateForm_Thread_num2 = null;
List<teamDataItem> teamData;
List<string> pointFields;
List<string> flagNames;
Thread readInThread = null;
string url;
int elapsedTime;
readInWebpage readIn;
//-------------------------------------------------------------------------------------------------------------------
public Form1 ()
{
InitializeComponent();
teamData = new List<teamDataItem>();
pointFields = new List<string>() { };
flagNames = new List<string>() { };
url = "http://www.reddit.com/new/";
elapsedTime = 15;
readIn = new readInWebpage(url, "", elapsedTime, new Return_CallBack(updateForm));
button2.Enabled = false;
setupDataGridView.setup(ref pointFields, ref flagNames, ref dataGridView1); //used to set up the columns
}
//-------------------------------------------------------------------------------------------------------------------
private void button1_Click (object sender, EventArgs e) //start
{
checkWebsite();
}
//-------------------------------------------------------------------------------------------------------------------
private void button2_Click (object sender, EventArgs e) //stop
{
readInThread.Abort();
button2.Enabled = false; //change over the state of the buttons to prevent double click issues
button1.Enabled = true;
}
//-------------------------------------------------------------------------------------------------------------------
private void checkWebsite () //used to start the thread
{
this.readInThread = new Thread(readIn.runScan);
readInThread.Start();
button2.Enabled = true; //change over the state of the buttons to prevent double click issues
button1.Enabled = false;
}
//-------------------------------------------------------------------------------------------------------------------
private void updateForm (string downloadedData) //the delegate class to call back to
{
lock (thisLock) //lock (mutex style)
{
this.updateForm_Thread = new Thread(() => parseData.strip(teamData, downloadedData, pointFields, flagNames, new Return_FormData(updateForm_Data))); //parse the data
this.updateForm_Thread.Start();
//!!! when the updateForm_Data() function is called, this needs to be disposed of
}
}
//-------------------------------------------------------------------------------------------------------------------
private void updateForm_Data (List<teamDataItem> tempData, string rtbData) //call back by the parseData
{
lock (lockFormUpdate) //lock off the form update to one class
{
this.updateForm_Thread_num2 = new Thread(() => thread_UpdateFormSafe(tempData, rtbData)); //used to actually apply the updated data to the form
this.updateForm_Thread_num2.Start();
//!!! once the form has been updated this needs to be disposed of
}
}
//-------------------------------------------------------------------------------------------------------------------
private void thread_UpdateFormSafe (List<teamDataItem> tempData, string rtbData) //used to update the form data
{
//!!!!! I NEVER UPDATE THE COMBOBOX (needs to be done through new entry only)
//if index == -1, and newEntry == true, then it's a new entry
//if index != -1, and newEntry == false, then it's an old entry who's value changed
//if index == -1, and newEntry == false, then it's an old entry that was unchanged
for (int i = 0; i < tempData.Count; i++) //go through the list
{
if (tempData [i].newEntry == true) //if a new entry was added
{
this.addToDataGridView(tempData [i]);
}
else
{
if (tempData [i].index != -1) //an existing entry was updates
{
this.editDataGridView(tempData [i]);
}
}
}
//!!!! ADD - the richTextBox display
}
//-------------------------------------------------------------------------------------------------------------------
private void addToDataGridView (teamDataItem tempEntry) //updates dataGridView1 by adding a new item (in thread safe)
{
if (this.dataGridView1.InvokeRequired) //if the calling thread is different then the one that created dataGridView1
{
SetDGV_New_Callback dgvCall = new SetDGV_New_Callback(addToDataGridView);
this.Invoke(dgvCall, new object [] { tempEntry });
}
else //if the same thread that created dataGridView1
{
dataGridView1.Rows.Add();
dataGridView1.Rows [dataGridView1.RowCount - 1].HeaderCell.Value = tempEntry.teamName;
for (int i = 0; i < pointFields.Count; i++) //adds the point fields
{
dataGridView1 [i, (dataGridView1.Rows.Count - 1)].Value = tempEntry.points [i];
}
for (int i = 0; i < flagNames.Count; i++) //adds the flag fields
{
dataGridView1 [(pointFields.Count + i), (dataGridView1.Rows.Count - 1)].Value = tempEntry.flags [i]; //remember it must pass by the pointFields first
}
}
}
//-------------------------------------------------------------------------------------------------------------------
private void editDataGridView (teamDataItem tempEntry) //updates dataGridView1 by editing an existing line (in thread safe)
{
if (this.dataGridView1.InvokeRequired) //if the calling thread is different then the one that created dataGridView1
{
SetDGV_Edit_Callback dgvCall = new SetDGV_Edit_Callback(editDataGridView);
this.Invoke(dgvCall, new object [] { tempEntry });
}
else //if the same thread that created dataGridView1
{
int indexFound = -1;
for (int i = 0; i < dataGridView1.RowCount; i++) //looks for the index
{
if (tempEntry.teamName == dataGridView1.Rows [i].HeaderCell.Value.ToString()) //found the same team
{
indexFound = i;
break;
}
}
if (indexFound != -1) //we found an entry
{
for (int i = 0; i < pointFields.Count; i++) //adds the point fields
{
dataGridView1 [i, indexFound].Value = tempEntry.points [i];
}
for (int i = 0; i < flagNames.Count; i++) //adds the flag fields
{
dataGridView1 [(pointFields.Count + i), indexFound].Value = tempEntry.flags [i]; //remember it must pass by the pointFields first
}
}
//!!! No else in case a fluke happens and we need to add a new row instead (implement later)
}
}
//-------------------------------------------------------------------------------------------------------------------
}
//===================================================================================================================
}
Now you'll see I have left commented code in there relating to my problem, at least where I think it lies. I have these threads that are used to parse the data, as well as add the data to the form. The initial thread that is calls in the checkWebsite()
function, only calls that thread once, then there's a infinite while loop that has a timer (okay actually it's a subtract two DateTime values and if the time span is larger then x) that when executed reads in the webpage (using a cURl plugin).
I am going to assume that readInThread.Abort();
line is okay for stopping that thread.
Anyway that's side tracked from the real problem, which is I have threads starting for the updateForm()
and updateForm_Data()
functions, but I never end them. Now when I watched the resource manager the CPU usage never went above 13% (please don't patronize me on that, I am new at this okay), but the memory usage I would watch go up and down, but was always progressively getting higher and higher.
I feel the automated garbage collector is the problem here and those classes in the functions I listed above are not being disposed off.
My question then is (and sorry it took me so long to get to this) how can I detect when these threads are done with their job and dispose them of their resources? They will be called again, as you can see it's reoccuring, but based on my knowledge of threads, I am create new threads everytime, which means all new resources.
I appreciate any help I can get, once again this is the first time I have worked with threads on this level, which I know can be a risky move. If you care to see some of my other classes, I can provide those (for instance the class that reads in the webpage using cURl). Or if maybe you see something else that's causing the problem I am listening. If I get this program to function properly, it could be a base model for when I build programs that use Threading in the future (there's already one I am waiting to build once I know this works).
Once again Thanks for any help