I'm running the following code to start my threads, but they don't start as intended. For some reason, some of the threads start with the same objects (and some don't even start). If I try to debug, they start just fine (extra delay added by me clicking F10 to step through the code).
These are the functions in my forms app:
private void startWorkerThreads()
{
int numThreads = config.getAllItems().Count;
int i = 0;
foreach (ConfigurationItem tmpItem in config.getAllItems())
{
i++;
var t = new Thread(() => WorkerThread(tmpItem, i));
t.Start();
//return t;
}
}
private void WorkerThread(ConfigurationItem cfgItem, int mul)
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(10*mul);
}
this.Invoke((ThreadStart)delegate()
{
this.textBox1.Text += "Thread " + cfgItem.name + " Complete!\r\n";
this.textBox1.SelectionStart = textBox1.Text.Length;
this.textBox1.ScrollToCaret();
});
}
Anyone able to help me out?
Starting a thread doesn't really start the thread. Instead it schedules it for execution. I.e. at some point it will get to run when it is scheduled. Scheduling threads is a complex topic and an implementation detail of the OS, so your code should not expect a certain scheduling.
You're also capturing variables in your lambda. Please see this post (there is a section on Captured Variables) for the problems associated with doing that.
You just run into the (be me called) lambda error.
You provide the ConfigurationItem from the foreach loop directly. This leads to the fact, that all your threads get the same item (the last one).
To get this to work you have to create a reference for each item and apply this to each thread:
foreach (ConfigurationItem tmpItem in config.getAllItems())
{
i++;
var currentI = i;
var currentItem = tmpItem;
var t = new Thread(() => WorkerThread(currentItem, currentI));
t.Start();
//return t;
}
And you should also consider using a ThreadPool.
MSDN Description about how to use the ThreadPool
Short summary of differences here on SO
The problem seems to be there : () => WorkerThread(tmpItem, i)
I'm not used to Func<> but it seems to work like anonymous delegates in .NET 2.0. Thus, you may have a reference to the arguments of the WorkerThread() method. Hence, their values are retrieved later (when the thread actually runs).
In this case, you may already be at the next iteration of your main thread...
Try this instead :
var t = new Thread(new ParametrizedThreadStart(WorkerThread));
t.Start(new { ConfigurationItem = tmpItem, Index = i } );
[EDIT] Other implementation. More flexible if you need to pass new parameters to the thread in the future.
private void startWorkerThreads()
{
int numThreads = config.getAllItems().Count;
int i = 0;
foreach (ConfigurationItem tmpItem in config.getAllItems())
{
i++;
var wt = new WorkerThread(tmpItem, i);
wt.Start();
//return t;
}
}
private class WorkerThread
{
private ConfigurationItem _cfgItem;
private int _mul;
private Thread _thread;
public WorkerThread(ConfigurationItem cfgItem, int mul) {
_cfgItem = cfgItem;
_mul = mul;
}
public void Start()
{
_thread = new Thread(Run);
_thread.Start();
}
private void Run()
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(10 * _mul);
}
this.Invoke((ThreadStart)delegate()
{
this.textBox1.Text += "Thread " + _cfgItem.name + " Complete!\r\n";
this.textBox1.SelectionStart = textBox1.Text.Length;
this.textBox1.ScrollToCaret();
});
}
}
Do you really need to spawn threads manually (which is a rather expensive task) ? You could try to switch to the ThreadPool instead.
You can't assume that the threads will run in the same order they were called, unless you force it, and cause a dependency between them.
So the real question is - what is your goal ?
I think that the error is somewhere else. Here are some hints to help you debug :
Give a name containing to each thread, and display the thread name instead of the config item name :
this.textBox1.Text += "Thread " + Thread.Current.Name + " Complete!\r\n";
Display the content of config.getAllItems(), may be that some items has the same name (duplicated)
===========
Here are some additional information about multi threading with winforms:
dont create new Thread directly, use the ThreadPool instead :
ThreadPool.QueueUserWorkItem(state => { WorkerThread(tmpItem, i); });
If you really want to creat your threads, use this.BeginInvoke instead of this.Invoke your worker thread will finish sooner => less concurrent thread => better global performance
don't call Thread.Sleep in a loop, just do a big sleep: Thread.Sleep(10*mul*100);
I hope that this will help you.
Thanks to all of you!
I just implemented the threadpool, and that worked like a charm - with the added bonus of not spawning too many threads at once.
I'll have a look at the other solutions, too, but this time around the threadpool will save me from having to manually check for bozos with too many configs ;)
Related
I am relatively green to proper threading, I have this, it's modified for simplicity but it is essentially the same thing:
//Global:
N=2
bool[] mySwitches;
//In my main:
mySwitches = new bool[N];
for (int i = 0; i < N; i++)
{
ThreadList.Add(new Thread(() => Worker(i)));
ThreadList[i].Start();
}
//Outside of main:
Private Void Worker(int num)
{
while(true)
{
if (mySwitches[num]) //error happes here because num is equal to N, how?
{
//do something
}
}
}
As shown above, somehow a worker thread gets a value of num=N, I expect it to only reach N-1.
I know that i will get incremented after the Worker is created, is i somehow getting passed by reference instead of value?
I tried to fix the issue by putting a test before my while loop to return if num=N, but even with this provision I get the same error. This leads me to believe that num is incremented somehow after the thread starts.
I fixed this issue by putting a Sleep(20) directly after ThreadList[i].Start(), but I don't really like using Sleep, and it's clear I don't know how this threading scenario actually works.
Can anyone shed any light on this?
i is captured by its reference. Change your code as
for (int i = 0; i < N; i++)
{
var thInx = i;
ThreadList.Add(new Thread(() => Worker(thInx)));
ThreadList[thInx].Start();
}
For more info: http://csharpindepth.com/articles/chapter5/closures.aspx
I have a parallel algorithm which I have some barrier issues with. Before y'all scream "search" I can say I have looked at available posts and links, and I have followed the instructions for a barrier with Monitor.Wait and Monitor.PulseAll, but my issue is that all threads except the last one created (and initiated) is reached by the PulseAll from my main thread. Here are how the basic layout of the code is:
public static object signal = new object(); //This one is located as class variable, not in the method
public void RunAlgorithm(List<City> cities){
List<Thread> localThreads = new List<Thread>();
object[] temp = //some parameters here
for(int i = 0; i < numOfCitiesToCheck; i++){
Thread newThread = new Thread((o) => DoWork(o as object[]));
newThread.IsBackground = true;
newThread.Priority = ThreadPriority.AboveNormal;
newThread.Start(temp as object);
localThreads.Add(newThread);
}
//All threads initiated, now we pulse all
lock(signal){
Monitor.PulseAll(signal);
}
int counter = 0;
while(true){
if(counter == localThreads.Count){ break; }
localThreads[counter].Join();
counter++;
}
}
That's what done by the main thread (removed a few uneccessary pieces) and as stated before the main thread will always get stuck in the Join() on the last thread in the list.
This is how the method of the threads look like:
private void DoWork(object[] arguments){
lock(signal){
Monitor.Wait(signal);
}
GoDoWork(arguments);
}
Are there any other barriers I can use for this type of signaling? All I want is to let the main thread signal all threads at the same time so that they start at the same time. I want them to start at the same time inorder to have as close parallel as possible (I measure running time for the algorithm and a few other things). Is there any part of my barrier or code that is flawed (with the barrier I mean)? I tried running an instance with fewer threads and it still get stuck on the last one, I don't know why it is. I have confirmed by via VS debug that the last thread is sleeping (all other threads are !isAlive, while the last one isAlive = true).
Any help appreciated!
I managed to solve it using the Barrier class. Many thanks to Damien_The_Unbeliever! Still can't believe I haven't heard of it before.
public Barrier barrier = new barrier(1);
public void RunAlgorithm(List<City> cities){
List<Thread> localThreads = new List<Thread>();
object[] temp = //some parameters here
for(int i = 0; i < numOfCitiesToCheck; i++){
barrier.AddParticipant();
Thread newThread = new Thread((o) => DoWork(o as object[]));
newThread.IsBackground = true;
newThread.Priority = ThreadPriority.AboveNormal;
newThread.Start(temp as object);
localThreads.Add(newThread);
}
barrier.SignalAndWait();
int counter = 0;
while(true){
if(counter == localThreads.Count){ break; }
localThreads[counter].Join();
counter++;
}
}
private void DoWork(object[] arguments){
barrier.SignalAndWait();
GoDoWork(arguments);
}
The form I'm trying to develop has an array of 6 picture boxes and an array of 6 die images. I have a button that when clicked needs to create 6 threads that "roll" the dice, showing each image for a moment. The problem I'm having is that I need to call a method within button click after the dice have been rolled. I can get the dice to roll but the message box is displayed immediately. I've tried a few different ways and get various errors. In the non working version below, the program freezes. I've checked out a ton of resources but I'm just not grasping some concepts like Delegates and Invoke all that well.
Any help would be great! Here's my program
namespace testDice
{
public partial class Form1 : Form
{
private Image[] imgAr;
private PictureBox[] picBoxAr;
private Random r;
private Thread[] tArray;
private ThreadStart tStart;
private delegate void setTheImages();
public Form1()
{
InitializeComponent();
setImageArray();
setPicBoxAr();
}
private void setImageArray()
{
imgAr = new Image[6];
imgAr[0] = testDice.Properties.Resources.die6;
imgAr[1] = testDice.Properties.Resources.die1;
imgAr[2] = testDice.Properties.Resources.die2;
imgAr[3] = testDice.Properties.Resources.die3;
imgAr[4] = testDice.Properties.Resources.die4;
imgAr[5] = testDice.Properties.Resources.die5;
}
private void setPicBoxAr()
{
picBoxAr = new PictureBox[6];
picBoxAr[0] = pictureBox1;
picBoxAr[1] = pictureBox2;
picBoxAr[2] = pictureBox3;
picBoxAr[3] = pictureBox4;
picBoxAr[4] = pictureBox5;
picBoxAr[5] = pictureBox6;
}
private void button1_Click(object sender, EventArgs e)
{
roll();
//wait for threads to finish and update images--doesn't work
for (int n = 0; n < 6; n++)
{
while (tArray[n].IsAlive)
{
for (int i = 0; i < 6; i++)
{
this.picBoxAr[i].Update();
}
}
}
MessageBox.Show("Each die has its own thread");
}
private void roll()
{
this.tStart = new ThreadStart(RunAllDiceThreads);
this.tArray = new Thread[6];
for (int i = 0; i < 6; i++)
{
this.tArray[i] = new Thread(tStart);
this.tArray[i].Start();
}
}
private void RunAllDiceThreads()
{
int n = 0;
while (n < 50)
{
setImg();
Thread.Sleep(50);
n++;
}
for (int i = 0; i < 6; i++)
{
if (tArray[i] != null)
{
tArray[i].Abort();
tArray[i] = null;
}
}
}// end RunAllDiceThreads
private void setImg()
{
r = new Random();
for (int i = 0; i < 6; i++)
{
if (this.picBoxAr[i].InvokeRequired)
{
setTheImages s = new setTheImages(setImg);
// parameter mismatch error here
//this.Invoke(s, new object[] { imgAr[r.Next(6)] });
//Freezes here!!
this.Invoke(s);
}
else
{
this.picBoxAr[i].Image = imgAr[r.Next(6)];
}
}
}//end setImg
}// end class Form1
}//end namespace testDice
Sounds like you're getting a deadlock between your invocation of setting the images and your update of the picture boxes.
I'd recommend rethinking your program a bit. Your program almost seems to be built on the concept that you're modeling an individual die with an individual thread. Break up the state of the die from the state of the thread. For example, you might want to create a Die class which has a certain state to it, such as IsRolling, or CurrentValue. Use and modify objects of that class (and that class only) inside your loops in your worker threads. That way, you won't have to invoke back to your UI thread to update. The dependencies are a lot cleaner that way. You might want to create a Timer in your UI thread which periodically fires (say 10-30 times a second), reads the state of each of the dice, and updates the images that way. That's a lot safer in terms of deadlocks because you don't have any cyclic dependencies. It'll also likely produce a more attractive interface because your die images will update in a smoother, more predictable fashion.
Another rule of thumb... Don't call Thread.Abort() (see references). It's generally a lot safer to use a property of a Die object and simply read from that to update your UI.
You need to remove MessageBox.Show("Each die has its own thread"); from button1_Click.
Create a property to track how many threads have returned. When it hits 6 invoke MessageBox.Show("Each die has its own thread"); (you will probably want to put this call in its own method and invoke that method).
Your problem is that you are starting the threads, then while they are running showing the message box rather then waiting for the threads to return.
If you're able to work against the latest version of the .Net Framework, I would recommend making use of the System.Threading.Tasks namespace. The nice thing is that it encapsulates a lot of the multithreading details and makes things much cleaner. Here's a simple example.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TasksExample
{
class Program
{
static void Main(string[] args)
{
// holds all the tasks you're trying to run
List<Task> waitingTasks = new List<Task>();
// a simple object to lock on
object padlock = new object();
// simple shared value that each task can access
int sharedValue = 1;
// add each new task to the list above. The best way to create a task is to use the Task.Factory.StartNew() method.
// you can also use Task.Factory<RETURNVALUE>.StartNew() method to return a value from the task
waitingTasks.Add(Task.Factory.StartNew(() =>
{
// this makes sure that we don't enter a race condition when trying to access the
// shared value
lock (padlock)
{
// note how we don't need to explicitly pass the sharedValue to the task, it's automatically available
Console.WriteLine("I am thread 1 and the shared value is {0}.", sharedValue++);
}
}));
waitingTasks.Add(Task.Factory.StartNew(() =>
{
lock (padlock)
{
Console.WriteLine("I am thread 2 and the shared value is {0}.", sharedValue++);
}
}));
waitingTasks.Add(Task.Factory.StartNew(() =>
{
lock (padlock)
{
Console.WriteLine("I am thread 3 and the shared value is {0}.", sharedValue++);
}
}));
waitingTasks.Add(Task.Factory.StartNew(() =>
{
lock (padlock)
{
Console.WriteLine("I am thread 4 and the shared value is {0}.", sharedValue++);
}
}));
waitingTasks.Add(Task.Factory.StartNew(() =>
{
lock (padlock)
{
Console.WriteLine("I am thread 5 and the shared value is {0}.", sharedValue++);
}
}));
waitingTasks.Add(Task.Factory.StartNew(() =>
{
lock (padlock)
{
Console.WriteLine("I am thread 6 and the shared value is {0}.", sharedValue++);
}
}));
// once you've spun up all the tasks, pass an array of the tasks to Task.WaitAll, and it will
// block until all tasks are complete
Task.WaitAll(waitingTasks.ToArray());
Console.WriteLine("Hit any key to continue...");
Console.ReadKey(true);
}
}
}
I hope this helps, and let me know if you need any more help.
I am studying parallelism and would like to know which way do you recommend for me to access other thead elements, for example, imagima I'll fill a combobox with some names, query the database I would do in parallel but I could not do a combobox.add (result) from within the task, which way do you recommend me?
a simple example to understand my question:
private void button1_Click (object sender, EventArgs e)
{
Task task = new Task (new Action (Count));
task.Start ();
}
void Count ()
{
for (int i = 0; i <99; i + +)
{
Thread.Sleep (1);
progressBar1.Value = i;
}
}
time to pass the value for the progressbar result in error
If you want to schedule a task that access UI controls, you need to pass the current synchronization context to the scheduler. If you do that the scheduler will make sure your task is executed on the correct thread. E.g.
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => {
// code that access UI controls
}, uiScheduler);
For more info see http://msdn.microsoft.com/en-us/library/dd997402.aspx
You cannot access controls on another thread directly. You must invoke them first. Read this article: http://msdn.microsoft.com/en-us/library/ms171728.aspx
This is about what is would look like if you took the article and translated it for your own use: (NOT TESTED)
delegate void SetProgressBarCallback();
private void SetProgressBar()
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.progressBar1.InvokeRequired)
{
SetProgressBarCallback d = new SettProgressBarCallback(SetProgressBar);
this.Invoke(d);
}
else
{
for(int i=0; i<99; i++)
{
Thread.Sleep(1);
progressBar1.Value = i;
}
}
}
Just a quick note... the UI in WinForms can only be updated from the UI thread. Perhaps you should consider using Control.Invoke to update your progressBar1.
Ryan's answer was correct but he put the sleep inside the invoke, that caused the program to hang. Here is a example that uses the same thing he did but it does not put the sleep in the invoke.
private void button1_Click (object sender, EventArgs e)
{
Task task = new Task (new Action (Count));
task.Start ();
}
void Count ()
{
for (int i = 0; i <99; i + +)
{
Thread.Sleep (1);
if(progressBar1.InvokeRequired)
{
int j = i; //This is required to capture the variable, if you do not do this
// the delegate may not have the correct value when you run it;
progressBar1.Invoke(new Action(() => progressBar1.Value = j));
}
else
{
progressBar1.Value = i;
}
}
}
You must do the int j = i to do variable capture, otherwise it could bring up the wrong value for i inside the loop.
I have a timer calling a function every 15 minutes, this function counts the amount of lines in my DGV and starts a thread for each lines (of yet another function), said thread parse a web page which can take anywhere from 1 second to 10 second to finish.
Whilst it does work fine as it is with 1-6 rows, anymore will cause the requests to time-out.
I want it to wait for the newly created thread to finish processing before getting back in the loop to create another thread without locking the main UI
for (int x = 0; x <= dataGridFollow.Rows.Count - 1; x++)
{
string getID = dataGridFollow.Rows[x].Cells["ID"].Value.ToString();
int ID = int.Parse(getID);
Thread t = new Thread(new ParameterizedThreadStart(UpdateLo));
t.Start(ID);
// <- Wait for thread to finish here before getting back in the for loop
}
I have googled a lot in the past 24 hours, read a lot about this specific issue and its implementations (Thread.Join, ThreadPools, Queuing, and even SmartThreadPool).
It's likely that I've read the correct answer somewhere but I'm not at ease enough with C# to decypher those Threading tools
Thanks for your time
to avoid the UI freeze the framework provide a class expressly for these purposes: have a look at the BackgroundWorker class (executes an operation on a separate thread), here's some infos : http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
http://msdn.microsoft.com/en-us/magazine/cc300429.aspx
Btw looks if I understand correctly you don't want to parallelize any operation so just wait for the method parsing the page to be completed. Basically for each (foreach look) row of your grid you get the id and call the method. If you want to go parallel just reuse the same foreach loop and add make it Parallel
http://msdn.microsoft.com/en-us/library/dd460720.aspx
What you want is to set off a few workers that do some task.
When one finishes you can start a new one off.
I'm sure there is a better way using thread pools or whatever.. but I was bored so i came up with this.
using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using System.Threading;
namespace WorkerTest
{
class Program
{
static void Main(string[] args)
{
WorkerGroup workerGroup = new WorkerGroup();
Console.WriteLine("Starting...");
for (int i = 0; i < 100; i++)
{
var work = new Action(() =>
{
Thread.Sleep(1000); //somework
});
workerGroup.AddWork(work);
}
while (workerGroup.WorkCount > 0)
{
Console.WriteLine(workerGroup.WorkCount);
Thread.Sleep(1000);
}
Console.WriteLine("Fin");
Console.ReadLine();
}
}
public class WorkerGroup
{
private List<Worker> workers;
private Queue<Action> workToDo;
private object Lock = new object();
public int WorkCount { get { return workToDo.Count; } }
public WorkerGroup()
{
workers = new List<Worker>();
workers.Add(new Worker());
workers.Add(new Worker());
foreach (var w in workers)
{
w.WorkCompleted += (OnWorkCompleted);
}
workToDo = new Queue<Action>();
}
private void OnWorkCompleted(object sender, EventArgs e)
{
FindWork();
}
public void AddWork(Action work)
{
workToDo.Enqueue(work);
FindWork();
}
private void FindWork()
{
lock (Lock)
{
if (workToDo.Count > 0)
{
var availableWorker = workers.FirstOrDefault(x => !x.IsBusy);
if (availableWorker != null)
{
var work = workToDo.Dequeue();
availableWorker.StartWork(work);
}
}
}
}
}
public class Worker
{
private BackgroundWorker worker;
private Action work;
public bool IsBusy { get { return worker.IsBusy; } }
public event EventHandler WorkCompleted;
public Worker()
{
worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(OnWorkerDoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OnWorkerRunWorkerCompleted);
}
private void OnWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (WorkCompleted != null)
{
WorkCompleted(this, EventArgs.Empty);
}
}
public void StartWork(Action work)
{
if (!IsBusy)
{
this.work = work;
worker.RunWorkerAsync();
}
else
{
throw new InvalidOperationException("Worker is busy");
}
}
private void OnWorkerDoWork(object sender, DoWorkEventArgs e)
{
work.Invoke();
work = null;
}
}
}
This would be just a starting point.
You could start it off with a list of Actions and then have a completed event for when that group of actions is finished.
then at least you can use a ManualResetEvent to wait for the completed event.. or whatever logic you want really.
Call a method directly or do a while loop (with sleep calls) to check the status of the thread.
There are also async events but the would call another method, and you want to continue from the same point.
I have no idea why the requests would timeout. That sounds like a different issue. However, I can make a few suggestions regarding your current approach.
Avoid creating threads in loops with nondeterministic bounds. There is a lot of overhead in creating threads. If the number of operations is not known before hand then use the ThreadPool or the Task Parallel Library instead.
You are not going to get the behavior you want by blocking the UI thread with Thread.Join. The cause the UI to become unresponsive and it will effectively serialize the operations and cancel out any advantage you were hoping to gain with threads.
If you really want to limit the number of concurrent operations then a better solution is to create a separate dedicated thread for kicking off the operations. This thread will spin around a loop indefinitely waiting for items to appear in a queue and when they do it will dequeue them and use that information to kick off an operation asynchronously (again using the ThreadPool or TPL). The dequeueing thread can contain the logic for limiting the number of concurrent operations. Search for information regarding the producer-consumer pattern to get a better understand of how you can implement this.
There is a bit of a learning curve, but who said threading was easy right?
If I understand correctly, what you're currently doing is looping through a list of IDs in the UI thread, starting a new thread to handle each one. The blocking issue you're seeing then could well be that it's taking too many resources to create unique threads. So, personally (without knowing more) would redesign the process like so:
//Somewhere in the UI Thread
Thread worker = new Thread(new ParameterizedThreadStart(UpdateLoWorker));
worker.Start(dataGridFollow.Rows);
//worker thread
private void UpdateLoWorker(DataRowCollection rows)
{
foreach(DataRow r in rows){
string getID = r.Cells["ID"].Value.ToString();
int ID = int.Parse(getID);
UpdateLo(ID);
}
}
Here you'd have a single non-blocking worker which sequentially handles each ID.
Consider using Asynchronous CTP. It's an asynch pattern Microsoft recently released for download. It should simplify asynch programming tremendouesly. The link is http://msdn.microsoft.com/en-us/vstudio/async.aspx. (Read the whitepaper first)
Your code would look something like the following. (I've not verified my syntax yet, sorry).
private async Task DoTheWork()
{
for(int x = 0; x <= dataGridFollow.Rows.Count - 1; x++)
{
string getID = dataGridFollow.Rows[x].Cells["ID"].Value.ToString();
int ID = int.Parse(getID);
task t = new Task(new Action<object>(UpdateLo), ID);
t.Start();
await t;
}
}
This method returns a Task that can be checked periodically for completion. This follows the pattern of "fire and forget" meaning you just call it and presumably, you don't care when it completes (as long as it does complete before 15 minutes).
EDIT
I corrected the syntax above, you would need to change UpdateLo to take an object instead of an Int.
For a simple background thread runner that will run one thread from a queue at a time you can do something like this:
private List<Thread> mThreads = new List<Thread>();
public static void Main()
{
Thread t = new Thread(ThreadMonitor);
t.IsBackground = true;
t.Start();
}
private static void ThreadMonitor()
{
while (true)
{
foreach (Thread t in mThreads.ToArray())
{
// Runs one thread in the queue and waits for it to finish
t.Start();
mThreads.Remove(t);
t.Join();
}
Thread.Sleep(2000); // Wait before checking for new threads
}
}
// Called from the UI or elsewhere to create any number of new threads to run
public static void DoStuff()
{
Thread t = new Thread(DoCorestuff);
t.IsBackground = true;
mActiveThreads.Add(t);
}
public static void DoStuffCore()
{
// Your code here
}