I can't quite explain to myself in clear terms why a Task spawn by a Timer works just fine but a Timer spawn by a Task does NOT.
All relevant code is included below so you can easily reproduce it.
Form.cs:
private void Form1_Load(object sender, EventArgs e)
{
ProcessDelayList list = new ProcessDelayList();
foreach (ProcessDelay p in list)
{
//this works
p.Start();
//this does NOT work
//Task.Factory.StartNew(() => p.Start());
}
}
ProcessDelayList.cs:
public class ProcessDelayList : List<ProcessDelay>
{
public ProcessDelayList()
{
Add(new ProcessDelay("Process 1", 2000));
Add(new ProcessDelay("Process 2", 4000));
Add(new ProcessDelay("Process 3", 6000));
Add(new ProcessDelay("Process 4", 8000));
Add(new ProcessDelay("Process 5", 10000));
}
}
ProcessDelay.cs:
public class ProcessDelay
{
private string name;
private int delay;
private Timer timer;
public ProcessDelay(string name, int delay)
{
this.name = name;
this.delay = delay;
}
public void Start()
{
timer = new Timer();
timer.Interval = delay;
timer.Tick += timer_Tick;
timer.Start();
}
private void timer_Tick(object sender, EventArgs e)
{
//these work either way, as long as the task
// is NOT spawn in the main loop.
//TimerProc();
TimerProcTask();
}
private void TimerProcTask()
{
Task.Factory.StartNew(() => TimerProc());
}
private void TimerProc()
{
timer.Stop();
MessageBox.Show(name, delay.ToString());
}
}
Ah, Timers. There are four of them in .NET, each with slightly different behaviors. You are using System.Windows.Forms.Timer.
This timer uses the Win32 message queue to fire timer events (WM_TIMER
). The thread that creates the timer is the one on which the callback method (timer_Tick
) is executed. The thread needs a message pump in order for the timer to execute.
Telling the task to run on the current SynchronizationContext will make it work:
Task.Factory.StartNew(() => p.Start(),
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.FromCurrentSynchronizationContext());
This actually marshals the call to happen on the UI thread, though, so it seems sort of pointless to me, if all you're doing is calling the p.Start()
method anyway (pretty much acts single threaded).
Note the remarks section of the System.Windows.Forms.Timer
class:
This Windows timer is designed for a single-threaded environment where UI threads are used to perform processing. It requires that the user code have a UI message pump available and always operate from the same thread, or marshal the call onto another thread.
You can use System.Threading.Timer
(or the System.Timers.Timer
wrapper of this class) if you want your timer calls to actually execute on a separate thread. If you need your timer callback to update the UI, you will need to marshal that UI update call to the UI thread. However, you can make sure that any processing-intensive work is done on a separate thread, and only the smallest amount of code (e.g. actual updating of the controls) is done on the UI thread to keep it responsive.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With