Pregunta

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.

¿Fue útil?

Solución 2

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.

Otros consejos

The winforms timer is a control and must be used by placing it on a form. You never add it to a control-collection, so I would not expect it to work properly. The documentation says the following

Implements a timer that raises an event at user-defined intervals. This timer is optimized for use in Windows Forms applications and must be used in a window.

Therefore, I would suggest that you use an instance of the System.Timers.Timer class. This class can be used anywhere.

Note that the Tick-event you use above, is called by another name in the System.Timer.Timer class, namely the Elapsed-event.

I don't yet understand why the Forms.Timer doesn't operate as expected but the following excellent article explains in detail how to marshal work onto the UI thread: http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top