I want to use a System.Windows.Forms.Timer
to ensure that an event fires on the UI thread of an excel addin I'm creating. I construct the timer as follows:
private System.Windows.Forms.Timer _timer;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Debug.WriteLine("ThisAddIn_Startup:" + Thread.CurrentThread.ManagedThreadId);
_timer = new System.Windows.Forms.Timer();
_timer.Tick += new EventHandler(TimerEventHandler);
_timer.Interval = 500;
}
The timer is fired by a COM event from a library I am using:
private void OnEvent()
{
_timer.Start();
}
I then expect the _timer
to call the following method when it ticks:
public void TimerEventHandler(object sender, EventArgs args)
{
_timer.Stop();
Debug.WriteLine("Tick: " + Thread.CurrentThread.ManagedThreadId);
}
As I understand, when I create the timer in the Addin thread, even though it is started from another thread (COM event in this case), it should fire on the thread that it was created on, i.e. the addin thread. However, this doesn't happen.
I have implemented this exact mechanism in an RTDServer
I wrote in the past (as outlined by Kenny Kerr) and it works as expected but the _timer
in this scenario never ticks.
I have also read other SO articles that point to the same behavior and can't figure out what is different about my addin setup?
EDIT: The OnEvent() method is fired.
I initially meant to post this as comment, but it turned to be too long.
Firstly, your thread structure is a bit confusing to me, the way you described it. Put Debug.WriteLine("OnEvent:" + Thread.CurrentThread.ManagedThreadId)
inside OnEvent
and let us know all thread IDs you see from your debug output.
That said, the rules are:
You should create WinForms' Timer
object on an STA thread, and the thread should be configured as STA before it starts.
This thread may or may not be the main UI thread (where your main form was created), but it still should execute a message loop (with Application.Run) for timer events to fire. There are other ways of pumping messages, but generally you do not control them from .NET code.
You should handle the events sourced by WinForms' Timer
on the same thread it was created. You can then 'forward' these events to another thread context if you like (using SynchronizationContext Send
or Post
) but I can't think of any reasons for such complexity.
The answer by @Maarten actually suggests the right way of doing it, in my opinion.