?
Solved

How to know when all threads using ThreadWorker class have completed

Posted on 2014-10-08
9
Medium Priority
?
190 Views
Last Modified: 2014-10-13
I have an issue with an application that loads a lot of data into memory and then keeps it updated.
The pipe that we connect through is slow and so I have divided the TCP requests to load individual major components concurrently rather than sequentially.
public void SetAllMeetings(Boolean addEvents)
        {
            try
            {
                meetings = new List<MeetingDetails>();
                string request = Requests.GetGenericString(system, systemId, "Get_Meetings", "All", "", "");
                string response = requestStream.GetResponse(request);

                XmlDocument xml_doc = new XmlDocument();
                xml_doc.LoadXml(response);
                XmlNodeList meetingNodes = xml_doc.SelectNodes("Response/Meetings/Meeting");
                foreach (XmlNode meeting in meetingNodes)
                {
                    StartMeetingCreationThread(meeting);
                }   
            }
            catch (Exception ex)
            {
                ExceptionStruct temp = new ExceptionStruct(DateTime.Now, "RaceDay", ex.Message, ex.StackTrace.ToString());
                exceptionLogger.Add(temp);
            }
        }

public void StartMeetingCreationThread(XmlNode meeting)
        {
            MeetingCreationWorker worker = new MeetingCreationWorker(meeting, ref meetings, system, systemId, ref exceptionLogger);
            Thread thread = new Thread(worker.DoWork);
            thread.Priority = ThreadPriority.Normal;
            thread.IsBackground = true;
            thread.Start();
        }
 
public class MeetingCreationWorker : ThreadWorker

public override void DoWork()
        {
            try
            {
                MeetingDetails temp = new MeetingDetails(updateStream, system, systemId, meeting.SelectSingleNode("Meeting_Number").InnerText, ref exceptionLogger);   
                temp.SetEventDetails();     
                meetings.Add(temp);
            }
            catch (Exception ex)
            {
                ExceptionStruct temp = new ExceptionStruct(DateTime.Now, "RaceDay", ex.Message, ex.StackTrace.ToString());
                exceptionLogger.Add(temp);
            }
            finally
            {
                updateStream = null;
            }
        }

Open in new window

.

Each meeting now loads at the same time by creating a meeting object and all sub-objects.

How can I check that all the DoWork() procedures in the ThreadWorker object threads have finished and so the loading is complete from the main thread?

I had thought of creating a List<MeetingCreationWorker> and checking that they had completed using an internal variable. I would then, inside a loop, wait until every variable had been set to a value before moving on.
Does anyone have a better idea?

The MeetingCreationWorker objects and corresponding threads are discarded after the initial load.
0
Comment
Question by:jetbet
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 5
  • 4
9 Comments
 
LVL 34

Expert Comment

by:it_saige
ID: 40369933
I, personally, would use an event:
public void SetAllMeetings(Boolean addEvents)
{
	try
	{
		meetings = new List<MeetingDetails>();
		string request = Requests.GetGenericString(system, systemId, "Get_Meetings", "All", "", "");
		string response = requestStream.GetResponse(request);

		XmlDocument xml_doc = new XmlDocument();
		xml_doc.LoadXml(response);
		XmlNodeList meetingNodes = xml_doc.SelectNodes("Response/Meetings/Meeting");
		foreach (XmlNode meeting in meetingNodes)
		{
			StartMeetingCreationThread(meeting);
		}
	}
	catch (Exception ex)
	{
		ExceptionStruct temp = new ExceptionStruct(DateTime.Now, "RaceDay", ex.Message, ex.StackTrace.ToString());
		exceptionLogger.Add(temp);
	}
}

public void StartMeetingCreationThread(XmlNode meeting)
{
	MeetingCreationWorker worker = new MeetingCreationWorker(meeting, ref meetings, system, systemId, ref exceptionLogger);
	worker.OnThreadWorkerCompleted += ThreadWorkerCompleted;
	Thread thread = new Thread(worker.DoWork);
	thread.Priority = ThreadPriority.Normal;
	thread.IsBackground = true;
	thread.Start();
}

public void ThreadWorkerCompleted(object sender, ThreadWorkerCompletedEventArgs e)
{
	// Do something meaningful here...
}
 
public class MeetingCreationWorker : ThreadWorker
{
	#region ThreadWorderCompleted Memebers
	[NonSerialized]
	private ThreadWorkerCompletedEventHandler _threadWorkerCompleted;
	public event ThreadWorkerCompletedEventHandler ThreadWorkerCompleted
	{
		add { _threadWorkerCompleted += value; }
		remove { _threadWorkerCompleted -= value; }
	}

	protected virtual void OnThreadWorkerCompleted(object sender, ThreadWorkerCompletedEventArgs e)
	{
		ThreadWorkerCompletedEventHandler handler = _threadWorkerCompleted;
		if (handler != null)
			handler(sender, e);
	}
	#endregion

	public override void DoWork()
	{
		try
		{
			MeetingDetails temp = new MeetingDetails(updateStream, system, systemId, meeting.SelectSingleNode("Meeting_Number").InnerText, ref exceptionLogger);   
			temp.SetEventDetails();     
			meetings.Add(temp);
		}
		catch (Exception ex)
		{
			ExceptionStruct temp = new ExceptionStruct(DateTime.Now, "RaceDay", ex.Message, ex.StackTrace.ToString());
			exceptionLogger.Add(temp);
		}
		finally
		{
			updateStream = null;
		}
		OnThreadWorkerCompleted(this, new ThreadWorkerCompletedEventArgs(meeting, system, systemId));
	}
}

public delegate void ThreadWorkerCompletedEventHandler(object sender, ThreadWorkerCompletedEventArgs e);

public class ThreadWorkerCompletedEventArgs : EventArgs
{
	// Ideally you really want to use items that can identify the thread.
	public XmlNode Meeting ( get; private set; }
	// Just guessing that a system is a string
	public string System { get; private set; }
	// Just guessing that a systemId is an integer
	public int SystemID { get; private set; } 

	private ThreadWorkerCompletedEventArgs()
	{
		Meeting = default(XmlNode);
		SystemID = default(int);
		System = default(string);
	}

	public ThreadWorkerCompletedEventArgs(XmlNode Meeting, string System, int SystemID)
	{
		this.Meeting = Meeting;
		this.System = System;
		this.SystemID = SystemID;
	}
}

Open in new window


-saige-
0
 
LVL 34

Expert Comment

by:it_saige
ID: 40369958
Ofcourse, before you implement your own Completed event, I would first check to see if the ThreadWorker class you are dealing with implements it's own Completed event.  You might find some other useful events that the class implements, i.e Progress, Canceled and such.

-saige-
0
 

Author Comment

by:jetbet
ID: 40375930
Thanks for your input.
The thread worker class is a construct suggested by MS that I use as a base class whenever I need a new Thread.

I have solved the issue using my original idea of adding a Boolean variable to the MeetingCreationWorker and inside the "finally" block of the DoWork() function that I can then Query using the following code on the main thread.

public void StartMeetingCreationThread(XmlNode meeting)
        {
            MeetingCreationWorker worker = new MeetingCreationWorker(meeting, ref meetings, system, systemId, showAllRunners, ref raceSportLinks, oddsStoreURL, oddsStoreTimeout, triggerEvents, JetbetDate, ref exceptionLogger);
            meetingCreationList.Add(worker);
            Thread thread = new Thread(worker.DoWork);
            thread.Priority = ThreadPriority.Normal;
            thread.IsBackground = true;
            thread.Start();
        }
// From main thread
....

foreach (XmlNode meeting in meetingNodes)
                {
                    StartMeetingCreationThread(meeting);
                }

                while (meetingCreationList.Count > 0)
                {
                    for (int i = meetingCreationList.Count - 1; i > -1; i--)
                    {
                        if (meetingCreationList[i].finished)
                        {
                            meetingCreationList.RemoveAt(i);
                        }
                    }
                    Thread.Sleep(1000);
                }

Open in new window

0
Create CentOS 7 Newton Packstack Running Keystone

A bug was filed against RDO for the installation of Keystone v3. This guide is designed to walk you through the configuration for using Keystone v3 with Packstack. You will accomplish this using various repos and the Answers file.

 

Author Comment

by:jetbet
ID: 40378626
I've requested that this question be closed as follows:

Accepted answer: 0 points for jetbet's comment #a40375930

for the following reason:

No better solutions were offered
0
 
LVL 34

Expert Comment

by:it_saige
ID: 40375968
I understand that you used your original idea.  However, using an event to trigger the completion of a thread is more efficient then using a Thread.Sleep().

http://blogs.msmvps.com/peterritchie/2007/04/26/thread-sleep-is-a-sign-of-a-poorly-designed-program/

-saige-
0
 

Author Comment

by:jetbet
ID: 40375974
That is a good point but the issue is that I do not want the process to move on until all Creation Threads have completed their task.
I may have misunderstood your answer on how you suggested this was to be achieved with waiting for the 20 or so triggers.
0
 
LVL 34

Accepted Solution

by:
it_saige earned 2000 total points
ID: 40376062
Not a problem.  There are a couple of varying ways to handle waiting for the threads to complete.  One way is to use either a manual or auto reset event in conjunction with the completed event already suggested, the trick to this method is how to proplerly determine when the reset event would be signaled that all thread had completed.  One way would be to use a boolean value to indicate that you are creating threads and then when you are finished creating your threads, you set this boolean value to false to signal to the completed event that it is okay to start checking the for the existence of the remaining threads.  When there are no more threads to process and no more threads to add, the completed event would signal the reset event that it is okay to continue processing.  So lets put it all together:
using System;
using System.Collections.Generic;
using System.Threading;

namespace CSharpThreadExampleExample
{
	public class Worker
	{
		#region ThreadWorderCompleted Memebers
		[NonSerialized]
		private ThreadWorkerCompletedEventHandler _threadWorkerCompleted;
		public event ThreadWorkerCompletedEventHandler ThreadWorkerCompleted
		{
			add { _threadWorkerCompleted += value; }
			remove { _threadWorkerCompleted -= value; }
		}

		protected virtual void OnThreadWorkerCompleted(object sender, ThreadWorkerCompletedEventArgs e)
		{
			ThreadWorkerCompletedEventHandler handler = _threadWorkerCompleted;
			if (handler != null)
				handler(sender, e);
		}
		#endregion

		private bool shouldStop = false;
		private int id = -1;
		public int ID
		{
			get { return id; }
			private set 
			{ 
				if (!value.Equals(id))
					id = value;
			}
		}

		public void DoWork()
		{
			int count = 0;
			while (!shouldStop)
			{
				Console.WriteLine(string.Format("Worker Thread {0}: Working ({1} passes)...", ID, count + 1));
				// Simulating long running process.
				Thread.Sleep(200);
				count++;
				if (count == 5)
					shouldStop = true;
			}
			Console.WriteLine(string.Format("Worker Thread {0}: Terminating gracefully", ID));
			OnThreadWorkerCompleted(this, new ThreadWorkerCompletedEventArgs(ID));
		}

		public void RequestStop()
		{
			shouldStop = true;
		}

		private Worker() { ;}

		public Worker(int ID)
		{
			id = ID;
		}
	}

	class Program
	{
		static AutoResetEvent reset = new AutoResetEvent(false);
		static List<Worker> workers = new List<Worker>();
		static bool creatingThreads = false;

		static void Main(string[] args)
		{
			creatingThreads = true;
			for (int i = 0; i < 5; i++)
				CreateAndStartThreads(i + 1);
			Console.WriteLine("Settings the reset event to block the main thread");
			reset.WaitOne();
			Console.WriteLine("Finished executing all threads.");
			Console.ReadLine();
		}

		public static void CreateAndStartThreads(int ID)
		{
			Worker worker = new Worker(ID);
			worker.ThreadWorkerCompleted += OnThreadWorkerCompleted;
			Thread thread = new Thread(worker.DoWork) { Priority = ThreadPriority.Normal, IsBackground = true };
			thread.Start();
			Console.WriteLine(string.Format("Adding worker with ID - {0}", ID));
			workers.Add(worker);

			if (ID == 5)
				creatingThreads = false;
		}

		public static void OnThreadWorkerCompleted(object sender, ThreadWorkerCompletedEventArgs e)
		{
			if (sender is Worker)
			{
				Console.WriteLine(string.Format("Removing worker with ID - {0}", (sender as Worker).ID));
				workers.Remove(sender as Worker);
				if (!creatingThreads && workers.Count == 0)
					reset.Set();
			}
		}
	}

	public delegate void ThreadWorkerCompletedEventHandler(object sender, ThreadWorkerCompletedEventArgs e);

	public class ThreadWorkerCompletedEventArgs : EventArgs
	{
		// Ideally you really want to use items that can identify the thread.
		public int ID { get; private set; }

		private ThreadWorkerCompletedEventArgs()
		{
			ID = default(int);
		}

		public ThreadWorkerCompletedEventArgs(int ID)
		{
			this.ID = ID;
		}
	}
}

Open in new window


-saige-
0
 

Author Closing Comment

by:jetbet
ID: 40378627
Thanks for the example.

I have adjusted it for my code and it works great. Hopefully you will get these points.
0
 
LVL 34

Expert Comment

by:it_saige
ID: 40378646
It was my pleasure.

-saige-
0

Featured Post

VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Performance in games development is paramount: every microsecond counts to be able to do everything in less than 33ms (aiming at 16ms). C# foreach statement is one of the worst performance killers, and here I explain why.
The article shows the basic steps of integrating an HTML theme template into an ASP.NET MVC project
Michael from AdRem Software explains how to view the most utilized and worst performing nodes in your network, by accessing the Top Charts view in NetCrunch network monitor (https://www.adremsoft.com/). Top Charts is a view in which you can set seve…
Add bar graphs to Access queries using Unicode block characters. Graphs appear on every record in the color you want. Give life to numbers. Hopes this gives you ideas on visualizing your data in new ways ~ Create a calculated field in a query: …
Suggested Courses

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question