I want to launch a bunch of child processes and then wait for all the processes to be completed before proceeding. All works fine. But I want to introduce a delay between starting each consecutive process. The problem I face is that the Exited event from the child process can happen during the delay and I am not able to receive that event in the parent process while it is asleep.
please advice.
Here is some code:
public void SetupProcess(string program, string args)
{
cmd = new Process();
cmd.StartInfo.FileName = program;
cmd.StartInfo.Arguments = args;
cmd.EnableRaisingEvents = true;
//cmd.StartInfo.Verb = "Print";
cmd.StartInfo.CreateNoWindow = true;
cmd.Exited += new EventHandler(cmd_Exited);
}
public bool StartProcess(int delay)
{
bool retval = false;
if (cmd.Start())
{
Thread.Sleep(delay);
}
return retval;
}
private void cmd_Exited(object sender, EventArgs e)
{
pIsRunning = false;
if (Exited != null)
{
Console.WriteLine("Process exited. notfying queue...");
Exited(this, null); // signal the queue to run the next process
}
}
I took the time to implement GrawCube's suggestion. The code below will ensure:
1) No more than MaxConcurrent processes are started at a time (typical, for example no more than 4 processes on a Quad)
2) There is a certain Delay introduced between starting consecutive process (in my case the messaging service imposed this constraint)
Here is the code, hope that helps:
class ProcessExQueue
{
public int MaxConcurrent { get; set; }
public int Delay { get; set; }
public Action Done;
private List<Timer> _timers;
private List<ProcessEx> Servers = new List<ProcessEx>();
byte _running = 0;
private DateTime _lastStart;
public ProcessExQueue()
{
MaxConcurrent = 4;
Delay = 15000;
_timers = new List<Timer>();
}
public void Add(ProcessEx Server)
{
Servers.Add(Server);
Server.Exited += cmd_Exited;
Server.Started += cmd_Started;
}
public void RunProcessTimer(int delay)
{
System.Timers.Timer timer = new System.Timers.Timer();
timer.Elapsed += new System.Timers.ElapsedEventHandler(DoTask);
timer.Interval = delay;
timer.AutoReset = false; // only first time
timer.Start();
_timers.Add(timer);
Console.WriteLine("timer started with delay {0} # {1} ", delay, DateTime.Now);
}
void DoTask(object source, System.Timers.ElapsedEventArgs e)
{
Console.WriteLine("timer call # {0}", DateTime.Now);
lock (this)
{
if (Servers.Count > 0 && _running <= MaxConcurrent)
{
ProcessEx ToRun = Servers[0];
ToRun.Starter = (Timer)source;
Servers.Remove(ToRun);
bool success = ToRun.StartProcess();
if (success)
_running++;
}
else
{
Console.WriteLine("nothing started, queue size {0} running {1}", Servers.Count, _running);
}
}
}
public void StartFirstProcesses()
{
for (int i = 0; i < Servers.Count; i++)
{
RunProcessTimer(i == 0 ? 1000 : Delay*(i));
}
}
private void cmd_Exited(object sender, EventArgs e)
{
Console.WriteLine("Queue: process exited {0}.", ((ProcessEx)sender).GetPID());
// find the timer and get rid of it;
//TODO
Timer found = _timers.FirstOrDefault(t => t == ((ProcessEx)sender).Starter);
if (found != null)
{
found.Enabled = false;
}
lock (this)
{
_running--;
}
if (Servers.Count == 0) // no more processes to run
{
Console.WriteLine("No more processes to run. some threads may be asleep");
if (_running == 0)
{
Console.WriteLine("disarming all timers");
_timers.ForEach(t=> t.Enabled=false);
Done();
}
}
else
{
if (Servers.Count > 0 && _running <= MaxConcurrent)
{
TimeSpan tspan = (DateTime.Now - _lastStart);
RunProcessTimer(tspan.Milliseconds < Delay?Delay - tspan.Milliseconds:1);
}
}
}
private void cmd_Started(object sender, EventArgs e)
{
Console.WriteLine("Queue: process started {0}", ((ProcessEx)sender).GetPID());
_lastStart = DateTime.Now;
}
}
class ProcessEx
{
public Timer Starter { get; set; }
public bool IsRunning
{
get
{
return pIsRunning;
}
}
private bool pIsRunning = false;
public delegate void OutputEventHandler(ProcessEx sender, string Output, bool IsError);
public delegate void StatusEventHandler(ProcessEx sender, EventArgs e);
public event StatusEventHandler Started;
public event StatusEventHandler Exited;
private Process cmd;
public int GetPID()
{
return cmd.Id;
}
public bool StartProcess()
{
bool retval = false;
if (cmd.Start())
{
pIsRunning = true;
retval = true;
Console.WriteLine("Running {0} {1}", cmd.StartInfo.FileName, cmd.StartInfo.Arguments);
Console.WriteLine("Process Id {0}", cmd.Id);
if (Started != null)
Started(this, null);
}
return retval;
}
public void KillProcess()
{
if (IsRunning)
{
cmd.Kill();
}
}
public void SetupProcess(string program, string args)
{
cmd = new Process();
cmd.StartInfo.FileName = program;
cmd.StartInfo.Arguments = args;
cmd.EnableRaisingEvents = true;
//cmd.StartInfo.Verb = "Print";
cmd.StartInfo.CreateNoWindow = true;
cmd.Exited += new EventHandler(cmd_Exited);
}
private void cmd_Exited(object sender, EventArgs e)
{
pIsRunning = false;
if (Exited != null)
{
Console.WriteLine("thread {0} received exit signal", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Process {0} exited. notfying queue...", ((Process)sender).Id);
Exited(this, null); // signal the queue to run the next process
}
}
}
// usage
class Program
{
static void Main(string[] args)
{
ProcessEx s = new ProcessEx();
s.SetupProcess(#"C:\MyP\MBN\Dev\Test\Hello\bin\Debug\Hello.exe", "process 0");
ProcessExQueue q = new ProcessExQueue();
q.Add(s);
s = new ProcessEx();
s.SetupProcess(#"C:\MyP\MBN\Dev\Test\Hello\bin\Debug\Hello.exe", "process 1");
q.Add(s);
s = new ProcessEx();
s.SetupProcess(#"C:\MyP\MBN\Dev\Test\Hello\bin\Debug\Hello.exe", "process 2");
q.Add(s);
q.Done = () => Console.WriteLine("Done!");
q.StartFirstProcesses();
/*
Thread t = new Thread(q.StartFirstProcesses);
t.Start();
*/
Console.ReadKey();
}
}
// sample process
class Program
{
static void Main(string[] args)
{
if (args.Count() > 0)
{
Console.WriteLine(Process.GetCurrentProcess().Id);
Thread.Sleep(5000);
Console.WriteLine("exiting... press any key");
Console.ReadKey();
}
}
}
I think you may be looking for the Thread.Join method?
https://msdn.microsoft.com/en-us/library/system.threading.thread.join%28v=vs.110%29.aspx
Which allows your parent thread to wait on a child thread.
I like the Timer solution suggested by GrawCube. I implemented this without the use of a timer, but the timer solution may be cleaner. Credit also goes to Deadly-Bagel for posting the original code which I modified to add delay and cpu threshold. Here is the code:
class Program
{
static void Main(string[] args)
{
ProcessEx s = new ProcessEx();
s.SetupProcess(#"C:\MyP\MBN\Dev\Test\Hello\bin\Debug\Hello.exe", "process 0");
ProcessExQueue q = new ProcessExQueue();
q.Add(s);
s = new ProcessEx();
s.SetupProcess(#"C:\MyP\MBN\Dev\Test\Hello\bin\Debug\Hello.exe", "process 1");
q.Add(s);
s = new ProcessEx();
s.SetupProcess(#"C:\MyP\MBN\Dev\Test\Hello\bin\Debug\Hello.exe", "process 2");
q.Add(s);
Thread t = new Thread(q.StartFirstProcesses);
t.Start();
Console.ReadKey();
}
}
class ProcessExQueue
{
private List<ProcessEx> Servers = new List<ProcessEx>();
public int MaxConcurrent { get; set; }
public int Delay { get; set; }
byte _running = 0;
public ProcessExQueue()
{
MaxConcurrent = 4;
Delay = 60000;
}
public void Add(ProcessEx Server)
{
Servers.Add(Server);
Server.Exited += cmd_Exited;
Server.Started += cmd_Started;
}
private bool RunNextProcess()
{
bool retval = false;
if (Servers.Count > 0)
{
ProcessEx ToRun = Servers[0];
Servers.Remove(ToRun);
retval = ToRun.StartProcess();
if (retval)
lock (this)
{
_running++;
}
}
Console.WriteLine("Thread Id {0} will sleep...", Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(Delay);
Console.WriteLine("Thread Id {0} woke up", Thread.CurrentThread.ManagedThreadId);
return retval;
}
public void StartFirstProcesses()
{
while (Servers.Count > 0 && _running <= MaxConcurrent)
{
RunNextProcess();
}
}
private void cmd_Exited(object sender, EventArgs e)
{
lock (this)
{
_running--;
}
Console.WriteLine("Queue: process exited {0}.", ((ProcessEx)sender).GetPID());
if(Servers.Count > 0 && _running <= MaxConcurrent)
{
RunNextProcess();
}
}
private void cmd_Started(object sender, EventArgs e)
{
Console.WriteLine("Queue: process started {0}", ((ProcessEx)sender).GetPID());
}
}
class ProcessEx
{
public bool IsRunning
{
get
{
return pIsRunning;
}
}
private bool pIsRunning = false;
public delegate void OutputEventHandler(ProcessEx sender, string Output, bool IsError);
public delegate void StatusEventHandler(ProcessEx sender, EventArgs e);
public event StatusEventHandler Started;
public event StatusEventHandler Exited;
private Process cmd;
public int GetPID()
{
return cmd.Id;
}
public bool StartProcess()
{
bool retval = false;
if (cmd.Start())
{
pIsRunning = true;
retval = true;
Console.WriteLine("Running {0} {1}", cmd.StartInfo.FileName, cmd.StartInfo.Arguments);
Console.WriteLine("Process Id {0}", cmd.Id);
if (Started != null)
Started(this, null);
}
return retval;
}
public void KillProcess()
{
if (IsRunning)
{
cmd.Kill();
}
}
public void SetupProcess(string program, string args)
{
// https://msdn.microsoft.com/en-us/library/h6ak8zt5(v=vs.110).aspx
cmd = new Process();
cmd.StartInfo.FileName = program;
cmd.StartInfo.Arguments = args;
cmd.EnableRaisingEvents = true;
//cmd.StartInfo.Verb = "Print";
cmd.StartInfo.CreateNoWindow = true;
cmd.Exited += new EventHandler(cmd_Exited);
}
private void cmd_Exited(object sender, EventArgs e)
{
pIsRunning = false;
if (Exited != null)
{
Console.WriteLine("thread {0} received exit signal", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Process {0} exited. notfying queue...", ((Process)sender).Id);
Exited(this, null); // signal the queue to run the next process
}
}
}
}
Related
I have a class with timer like below
public class helper
{
Timer timer = new Timer();
private int counter = 0;
private int returnCode = 0;
public int Process()
{
SetTimer();
Console.WriteLine("The application started ");
return counter;
}
public void SetTimer()
{
int optionalWay = 0;
// Create a timer with a two second interval.
timer = new System.Timers.Timer(2000);
// Hook up the Elapsed event for the timer.
timer.Elapsed += (sender, e) => OnTimedEvent(sender, e, optionalWay);
timer.AutoReset = true;
timer.Enabled = true;
}
private void OnTimedEvent(Object source, ElapsedEventArgs e, int optionalWay)
{
counter++;
Console.WriteLine("Timer is ticking");
if (counter == 10)
{
timer.Stop();
timer.Dispose();
returnCode = returnCode + 1;
}
}
}
I have main function like this below
public static void Main()
{
helper helper = new helper();
int code = helper.Process();
Console.WriteLine("Main " + code.ToString());
Console.ReadLine();
}
what I want to do is return to main when my timer is stopped, not before that
, my timer class is running fine, main is getting printed like below
So main should wait till the result from timer is 1. And then end process
The code is working as it should. There is nothing inside the helper.Process() function that can wait or block the execution, so the function is returning immediately to the main before the OnTimedEvent is even executed.
A workaround can be done by implementing an event in the helper class and raise that event after the timer completes its work. And the main can listen to that event and act accordingly.
public class helper
{
Timer timer = new Timer();
private int counter = 0;
private int returnCode = 0;
public event EventHandler<int> Done;
...
private void OnTimedEvent(Object source, ElapsedEventArgs e, int optionalWay)
{
counter++;
Console.WriteLine("Timer is ticking");
if (counter == 10)
{
timer.Stop();
timer.Dispose();
returnCode = returnCode + 1;
if (Done != null)
{
Done.Invoke(this, returnCode);
}
}
}
}
And in the Program.cs
static void Main(string[] args)
{
helper helper = new helper();
helper.Done += helper_Done;
helper.Process();
Console.ReadLine();
}
static void helper_Done(object sender, int e)
{
Console.WriteLine("Main " + e.ToString());
}
Update
The Timer class uses a new thread from ThreadPool to execute the Elapsed event handler. So it cannot return to the Main which is running on a different thread. In short: what you are trying to do cannot not be achieved with a Timer.
Here is another solution using Thread.Sleep() which will satisfy your requirement, but keep in mind using Thread.Sleep() like this is not recommended.
public class helper
{
private int counter = 0;
private int returnCode = 0;
public int Process()
{
Console.WriteLine("The application started ");
StartTimer(2000);
return returnCode;
}
private void StartTimer(int ms)
{
while (counter++ < 10)
{
System.Threading.Thread.Sleep(ms);
Console.WriteLine("Timer is ticking");
}
returnCode = returnCode + 1;
}
}
class Program
{
static void Main(string[] args)
{
helper helper = new helper();
int code = helper.Process();
Console.WriteLine("Main " + code.ToString());
Console.ReadLine();
}
}
Again, this is NOT a good practice to use Thread.Sleep for a delayed execution and Thread.Sleep is less accurate compare to Timer.Elapsed. Try to change the design of your application and use Event or Callback function.
Change the Process function of helper class to accept a callback:
public void Process(Action<int> callBack)
{
SetTimer();
Console.WriteLine("The application started ");
if (timer != null)
timer.Disposed += (o, e) => callBack(counter);
}
Change the main function to send the callback:
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
helper helper = new helper();
helper.Process(c => Console.WriteLine("Main " + c.ToString()));
Console.ReadLine();
}
I have two options in mind, one of them and the ugliest, is to loop until timer is stopped, basically doing so:
public class helper
{
Timer timer = new Timer();
private int counter = 0;
private int returnCode = 0;
private bool timerWorking = false;
public int Process()
{
SetTimer();
Console.WriteLine("The application started ");
while(timerWorking){}
return counter;
}
public void SetTimer()
{
// All the staff you already have
timerWorking = true;
}
private void OnTimedEvent(Object source, ElapsedEventArgs e, int optionalWay)
{
counter++;
Console.WriteLine("Timer is ticking");
if (counter == 10)
{
//All the staff you already have
timerWorking = false;
}
}
}
Or, the more elegant, passing or registering a callback to be executed once the ending point is reached:
public class helper
{
Timer timer = new Timer();
private int counter = 0;
private int returnCode = 0;
Action<int> _doAfterTimerEnds
public void Process(Action<int> doAfterTimerEnds)
{
SetTimer();
_doAfterTimerEnds = doAfterTimerEnds;
Console.WriteLine("The application started ");
}
public void SetTimer()
{
int optionalWay = 0;
// Create a timer with a two second interval.
timer = new System.Timers.Timer(2000);
// Hook up the Elapsed event for the timer.
timer.Elapsed += (sender, e) => OnTimedEvent(sender, e, optionalWay);
timer.AutoReset = true;
timer.Enabled = true;
}
private void OnTimedEvent(Object source, ElapsedEventArgs e, int optionalWay)
{
counter++;
Console.WriteLine("Timer is ticking");
if (counter == 10)
{
timer.Stop();
timer.Dispose();
returnCode = returnCode + 1;
_doAfterTimerEnds(returnCode)
}
}
}
public static void Main()
{
var returnCode = 0;
var helper = new helper();
helper.Process(code => returnCode = code);
while (returnCode != 1) {}
Console.WriteLine("Main " + returnCode);
Console.ReadLine();
}
UPDATE: I've tested this last version and it is working as expected.
I want to run say 50 processes but since the machine will choke at 5, I want to run 5 processes at a time and and keep starting the next one as soon as one of the 5 running processes finish. Please advice what's a good way to do this?
I can wait on the last process I launch by Process.WaitForExit but that doesn't do the trick since what if one of the earlier 4 processes finishes first.
Thanks
I had to create a similar bottleneck while writing an app to handle hundreds of Powershell commands. I ended up creating a "queue" class that contained a list of objects. The objects were another custom class that contained the Process and a few events.
I'd loop through the processes, setting them up to be run, then add them to the Queue class. At the end I'd call a RunFirstScripts function that looped through the first 20 in the list and called a Start function, then removed it from the list.
I bound the Exit event to a handler that ran the next server and then removed it from the queue.
class ServerQueue
{
private List<Script> Servers = new List<Script>();
public void Add(Script Server)
{
Servers.Add(Server);
Server.Exited += cmd_Exited;
}
public void RunNextScript()
{
if (Servers.Count > 0)
{
Script ToRun = Servers[0];
Servers.Remove(ToRun);
ToRun.StartProcess();
}
}
public void StartFirstScripts()
{
byte Running = 0;
while (Servers.Count > 0 && Running <= 20)
{
RunNextScript();
Running++;
}
}
private void cmd_Exited(object sender, EventArgs e)
{
RunNextScript();
}
}
EDIT Here's some code for the Script:
class Script
{
public string Output
{
get
{
return pOutput;
}
}
public string Errors
{
get
{
return pErrors;
}
}
public bool IsRunning
{
get
{
return pIsRunning;
}
}
private string pOutput = "";
private string pErrors = "";
private bool pIsRunning = false;
public delegate void OutputEventHandler(Script sender, string Output, bool IsError);
public delegate void StatusEventHandler(Script sender);
public event OutputEventHandler OutputDataReceived;
public event OutputEventHandler ErrorDataReceived;
public event StatusEventHandler Started;
public event StatusEventHandler Exited;
private Process cmd;
public void StartProcess()
{
pIsRunning = true;
cmd.Start();
cmd.BeginOutputReadLine();
cmd.BeginErrorReadLine();
Started(this);
}
public void KillProcess()
{
if (IsRunning)
{
cmd.Kill();
}
}
public void SetupScript()
{
cmd = new Process();
//configure Process (but don't start it yet)
}
private void cmd_Exited(object sender, EventArgs e)
{
pIsRunning = false;
Exited(this);
//do other stuff
}
private void cmd_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
OutputDataReceived(this, e.Data, false);
//do stuff
}
private void cmd_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
ErrorDataReceived(this, e.Data, true);
//do stuff
}
}
There's a tiny race condition, if the planets align correctly you could attempt to start the same process twice but the second attempt should fail (as it's already running) and the way it's set up it's extremely unlikely to ever occur. You can add some extra error trapping if you like, there should probably be a try/catch in StartProcess (this is modified from my own solution and as such missing some context).
EDIT
Here is my Process to work with all these bits:
Process cmd = new Process();
cmd.StartInfo.FileName = "PowerShell.exe";
cmd.StartInfo.Arguments = "-executionpolicy unrestricted -file \"" + TempFile.FullName + "\" -pwd " + Pwd;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.RedirectStandardError = true;
cmd.StartInfo.UseShellExecute = false;
cmd.StartInfo.CreateNoWindow = true;
cmd.EnableRaisingEvents = true;
cmd.OutputDataReceived += cmd_UpdateDataReceived;
cmd.ErrorDataReceived += cmd_ErrorDataReceived;
cmd.Exited += cmd_Exited;
here is the edited code for completeness. Full credit to Deadly-Bagel. Thanks,
class ServerQueue
{
private List<Script> Servers = new List<Script>();
public void Add(Script Server)
{
Servers.Add(Server);
Server.Exited += cmd_Exited;
}
public void RunNextScript()
{
if (Servers.Count > 0)
{
Script ToRun = Servers[0];
Servers.Remove(ToRun);
ToRun.StartProcess();
}
}
public void StartFirstScripts()
{
byte Running = 0;
while (Servers.Count > 0 && Running <= 20)
{
RunNextScript();
Running++;
}
}
private void cmd_Exited(object sender, EventArgs e)
{
RunNextScript();
}
}
class Script
{
public string Output
{
get
{
return pOutput;
}
}
public string Errors
{
get
{
return pErrors;
}
}
public bool IsRunning
{
get
{
return pIsRunning;
}
}
private string pOutput = "";
private string pErrors = "";
private bool pIsRunning = false;
public delegate void OutputEventHandler(Script sender, string Output, bool IsError);
public delegate void StatusEventHandler(Script sender, EventArgs e);
public event OutputEventHandler OutputDataReceived;
public event OutputEventHandler ErrorDataReceived;
public event StatusEventHandler Started;
public event StatusEventHandler Exited;
private Process cmd;
public void StartProcess()
{
pIsRunning = true;
cmd.Start();
cmd.BeginOutputReadLine();
cmd.BeginErrorReadLine();
Started(this, null);
}
public void KillProcess()
{
if (IsRunning)
{
cmd.Kill();
}
}
public void SetupScript()
{
cmd = new Process();
cmd.EnableRaisingEvents = true;
cmd.Exited += new EventHandler(cmd_Exited);
//configure Process (but don't start it yet)
}
private void cmd_Exited(object sender, EventArgs e)
{
pIsRunning = false;
Exited(this, null);
//do other stuff
}
private void cmd_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
OutputDataReceived(this, e.Data, false);
//do stuff
}
private void cmd_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
ErrorDataReceived(this, e.Data, true);
//do stuff
}
}
I'm creating an Windowns phone 8 app(c#), its a countdown interval timer, so there is prepare time(10 sec), work time(20 sec), rest time(10 sec). I have these variables
`TimeSpan prepInterval = new TimeSpan(0, 0, 0, 10);
TimeSpan workInterval = new TimeSpan(0, 0, 0, 20);
TimeSpan restInterval = new TimeSpan(0, 0, 0, 10);`
I can't wrap my head around having them implementing them one after another when they hit 0. So when prepare time is done, the work timer is to start and when thats finised, the rest timer is to start.
If you would like to have some more broken down logic in all of this, maybe you can create some classes based on a simple interface, like the following:
interface ITimerAction
{
int Seconds { get; set; }
bool Started { get; }
bool Completed { get; }
void OnStart();
void OnComplete();
}
interface ITimerActionList
{
void Add(ITimerAction action);
void Work();
event EventHandler OnCompletedEvent;
}
This would then allow you to create an abstract TimerAction class, and TimerActionList
abstract class TimerAction : ITimerAction
{
public virtual int Seconds
{
get;
set;
}
public virtual bool Completed
{
get;
protected set;
}
public virtual bool Started
{
get;
protected set;
}
public abstract void OnStart();
public abstract void OnComplete();
}
class TimerActionList : ITimerActionList
{
public event EventHandler OnCompletedEvent;
private readonly IList<ITimerAction> actions = new List<ITimerAction>();
private bool working = false;
private Thread myThread;
public void Add(ITimerAction action)
{
if (working)
{
throw new InvalidOperationException("Cannot add new timers when work is already in progress");
}
actions.Add(action);
}
protected virtual void DoWork()
{
working = true;
int currentStep = 0, maxSteps = actions.Count;
while (currentStep < maxSteps)
{
ITimerAction action = actions[currentStep];
if (!action.Started)
{
action.OnStart();
}
if (action.Completed)
{
currentStep++;
continue;
}
if (action.Seconds == 0)
{
action.OnComplete();
continue;
}
action.Seconds--;
Thread.Sleep(1000);
}
Completed();
}
public void Work()
{
if (working)
{
throw new InvalidOperationException("Already running!");
}
working = true;
myThread = new Thread(DoWork);
myThread.Start();
}
protected virtual void Completed()
{
myThread = null;
working = false;
actions.Clear();
var local = OnCompletedEvent;
if (local != null)
{
local.Invoke(this, EventArgs.Empty);
}
}
}
You could then write the classes that inherit from the TimerAction class, that could handle an action before and after the timer ran through :)
class PrepareTimer : TimerAction
{
public override void OnStart()
{
Console.WriteLine("Preparing");
Started = true;
}
public override void OnComplete()
{
Console.WriteLine("Prepare ready");
Completed = true;
}
}
class WorkTimer : TimerAction
{
public override void OnStart()
{
Console.WriteLine("Working");
Started = true;
}
public override void OnComplete()
{
Console.WriteLine("Work ready");
Completed = true;
}
}
class CoolDownTimer : TimerAction
{
public override void OnStart()
{
Console.WriteLine("Cooling down");
Started = true;
}
public override void OnComplete()
{
Console.WriteLine("Cooldown ready");
Completed = true;
}
}
And then you could test the code as such
static void Main(string[] args)
{
bool done = false;
ITimerActionList mylist = new TimerActionList();
mylist.Add(new PrepareTimer { Seconds = 1 });
mylist.Add(new WorkTimer { Seconds = 2 });
mylist.Add(new CoolDownTimer { Seconds = 1 });
mylist.OnCompletedEvent += (sender, e) =>
{
done = true;
};
mylist.Work();
while (!done)
{
// timer is running
}
Console.WriteLine("Done!");
}
(Console program, but i guess that also goes to demonstrate?)
Here's an example based on deathismyfriend's and Hans Passant's suggestions:
var start = new DateTime();
var stage = 0;
var timer = new System.Timers.Timer(100);
timer.Elapsed += (s, e) =>
{
var elapsed = DateTime.Now - start;
int duration = stage == 1 ? 20 : 10;
if (elapsed.TotalSeconds > duration)
{
start = DateTime.Now;
stage++;
if (stage > 2)
timer.Stop();
}
};
start = DateTime.Now;
stage = 0;
timer.Start();
I have custom thread which parses WiFi networks and updates the UI (DataGridView and graphs). Here is the thread method:
private void RefreshThread()
{
var watch = Stopwatch.StartNew();
while (true)
{
UpdateAllNetworks();
UpdateAllInterferences();
UpdateAllColors();
switch (ActivePage)
{
case Page.Start:
break;
case Page.Networks:
this.Invoke((MethodInvoker)delegate
{
UpdateDataGridWithNetworks();
ClearGraphs();
Draw24GHzGraph();
DrawSignalsOverTimeGraph();
});
break;
case Page.Channels:
break;
case Page.Analyze:
break;
default:
break;
}
watch.Stop();
int elapsedMs = (int) watch.ElapsedMilliseconds;
if (elapsedMs < Constants.NetworksRefreshThreadInterval)
Thread.Sleep(Constants.NetworksRefreshThreadInterval - elapsedMs);
}
}
Custom DataGridView:
public class CustomDataGridView : DataGridView
{
...
protected override void OnCellClick(DataGridViewCellEventArgs e)
{
base.OnCellClick(e);
int Index = e.RowIndex;
if (Index != -1)
{
DataGridViewRow row = Rows[Index];
PrimaryKeyForSelectedRow = row.Cells[KeyName].Value.ToString();
}
}
}
The DataGridView is my custom DataGrid where I have a click event handler. I have observed that sometimes the event handler isn't called but in most cases it is.
What could be the problem? Is it related to multithreading or the event isn't queued?
Your code blocks main thread, use separate thread for your network details update. Here is quick sample how it done.
class Program
{
static void Main(string[] args)
{
var helper = new Looper(5000, YourMethod_RefreshThread);
helper.Start();
}
private static void YourMethod_RefreshThread()
{
Console.WriteLine(DateTime.Now);
}
}
public class Looper
{
private readonly Action _callback;
private readonly int _interval;
public Looper(int interval, Action callback)
{
if(interval <=0)
{
throw new ArgumentOutOfRangeException("interval");
}
if(callback == null)
{
throw new ArgumentNullException("callback");
}
_interval = interval;
_callback = callback;
}
private void Work()
{
var next = Environment.TickCount;
do
{
if (Environment.TickCount >= next)
{
_callback();
next = Environment.TickCount + _interval;
}
Thread.Sleep(_interval);
} while (IsRunning);
}
public void Start()
{
if (IsRunning)
{
return;
}
var thread = new Thread(Work);
thread.Start();
IsRunning = true;
}
public void Stop()
{
this.IsRunning = false;
}
public bool IsRunning { get; private set; }
Question 1: I want to refresh a label by a work thread by delegate and invoke. It works well until i try to close the form. In closing event, I stop the work thread and then the UI thread while an exception occurs(Object disposed exception). It seems that form1 is disposed. I don't know what's wrong.
Question 2: When running this code, the memory usage keep increasing. I don't think it should take so much memory space. You can see find this by checking the task manager.
here's my code:
(.Net Framework 4, winforms)
Scheduler:
class Scheduler
{
private Thread[] workThreads = null;
//Scheduler started flag
public static bool bSchedulerStarted = false;
//Work threads stop event
public static EventWaitHandle stopWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
private static Scheduler self = null;
public static Scheduler getInstance()
{
if (self == null)
{
self = new Scheduler();
}
return self;
}
private Scheduler()
{
workThreads = new Thread[1];
}
private void CreateThread()
{
workThreads[0] = new Thread(Worker.doWork);
workThreads[0].IsBackground = true;
}
public void startUp()
{
if (!bSchedulerStarted)
{
stopWaitHandle.Reset();
CreateThread();
//Start all work threads
for (int i = 0; i < 1; i++)
{
workThreads[i].Start();
}
bSchedulerStarted = true;
}
}
public void stop()
{
if (!bSchedulerStarted)
return;
//Send stop event
stopWaitHandle.Set();
bSchedulerStarted = false;
if (workThreads != null)
{
//wait for all work threads to stop
for (int i = 0; i <1; i++)
{
if (workThreads[i] != null && workThreads[i].IsAlive)
workThreads[i].Join();
}
}
}
}
Worker:
class Worker
{
public static void doWork()
{
while (true)
{
if (Scheduler.stopWaitHandle.WaitOne(10, false) == true)
{
break;
}
Form1.count++;
Form1.sysMsgEvent.Set();
Thread.Sleep(10);
}
}
}
And form:
public partial class Form1 : Form
{
public static int count = 0;
public static EventWaitHandle sysMsgEvent = new EventWaitHandle(false, EventResetMode.AutoReset);
public static EventWaitHandle stopEvent = new EventWaitHandle(false, EventResetMode.ManualReset);
private EventWaitHandle[] waitEvent = null;
Thread UIThread = null;
private delegate void ShowMsg();
public Form1()
{
InitializeComponent();
waitEvent = new EventWaitHandle[2];
waitEvent[0] = stopEvent;
waitEvent[1] = sysMsgEvent;
}
public void UpdateUI()
{
while (true)
{
switch (EventWaitHandle.WaitAny(waitEvent))
{
case 0: //Stop UI thread
return;
case 1: //Refresh UI elements
updateLabel();
break;
default:
return;
}//switch
}//while
}
private void updateLabel()
{
if (label1.InvokeRequired)
{
ShowMsg d = new ShowMsg(updateLabel);
this.Invoke(d, null);
}
else
{
label1.Text = count.ToString();
}
}
private void Form1_Load(object sender, EventArgs e)
{
UIThread = new Thread(new ThreadStart(UpdateUI));
UIThread.Start();
Scheduler sc = Scheduler.getInstance();
sc.startUp();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Scheduler sc = Scheduler.getInstance();
sc.stop();
//stop UI thread
Form1.stopEvent.Set();
}
}