3

I have an object that uses a timer to occasionally poll for a resource and then raises an event whenever the poll finds something of note. I have looked at several other examples but can't seem to find a method to marshall the event back to the UI thread without extra code on the event handler on the UI thread. So my question is:

Is there any way to hide this extra effort from the users of my object?

For the purpose of discussion I will include a trivial example:

Imagine I have a form with 1 richtextbox:

private void Form1_Load(object sender, EventArgs e)
{
    var listener = new PollingListener();
    listener.Polled += new EventHandler<EventArgs>(listener_Polled);
}

void listener_Polled(object sender, EventArgs e)
{
    richTextBox1.Text += "Polled " + DateTime.Now.Second.ToString();
}

Also I have this object:

public class PollingListener
{
    System.Timers.Timer timer = new System.Timers.Timer(1000);
    public event EventHandler<EventArgs> Polled;
    public PollingListener()
    {
        timer.Elapsed +=new System.Timers.ElapsedEventHandler(PollNow);
        timer.Start();
    }

    void PollNow(object sender, EventArgs e)
    {
        var temp = Polled;
        if (temp != null) Polled(this, new EventArgs());

    }
}

If I run this, as expected it yields the exception

"Cross-thread operation not valid: Control 'richTextBox1' accessed from a thread other than the thread it was created on"

This makes sense to me, and I can wrap the event handler method differently as so:

void listener_Polled(object sender, EventArgs e)
{
    this.BeginInvoke(new Action(() => { UpdateText() }));
}
void UpdateText()
{
    richTextBox1.Text += "Polled " + DateTime.Now.Second.ToString();
}

But now the user of my object has to do this for any event that is raised from the timer event in my control. So, is there anything I can add to my PollingListener class that doesn't change the signature of it's methods to pass in extra references that would allow the user of my object to be oblivious of the marshaling event in the background to the UI thread?

Thanks for any input you may have.

deepee1
  • 12,878
  • 4
  • 30
  • 43
  • Just use a WinForms or WPF Timer. Cut most of this code. – H H Mar 01 '12 at 23:02
  • Those timers could block the UI thread if calling his polling operation from those timers could block for any notable amount of time. – meklarian Mar 01 '12 at 23:07
  • 1
    I personally don't think it's a good idea to try to hide the fact that you are on a background thread. This is something your users should be aware of (I'm assuming that by users you mean fellow programmers). A user might want the event to fire in a thread other than the default UI thread or not care at all what thread it fires on - it's their call, not yours (as the author of the utility class). – 500 - Internal Server Error Mar 01 '12 at 23:11
  • Although there are several good points here and admittedly the heavy use of the timer here cause a divergence from my more (less obvious) fundamental question, I was looking for perhaps some way to on construction of my object remember which thread created it and if it was a UI thread of sorts it would marshal appropriately. Considering the fragility of that design though I can't think of any way that would be a good idea. I assumed that there were several existing object that used threads internally, raised events, and bubbled them up to the UI thread for me wo/ Form refs, but apparently not. – deepee1 Mar 01 '12 at 23:52

3 Answers3

4

Added after comment:

You would need to pickup some latent detail that you can exploit to be able to accomplish that goal.

One thing that comes to mind is creating your own Forms/WPF timer at construction time and then use this and some synchronization to hide the details of coordination across threads. We can infer from your sample that construction of your poller should always happen in context of your consumer's thread.

This is a rather hack-ish way to accomplish what you want, but it can accomplish the deed because the construction of your poll-listener happens from the consumer's thread (which has a windows message pump to fuel the dispatches of Forms/WPF timers), and the rest of the operation of the class could occur from any thread as the forms Timer's tick will heartbeat from the original thread. As other comments and answers have noted, it would be best to reassess and fix the operating relationship between your polling operations and the consumer.

Here is an updated version of the class, PollingListener2 that uses a ManualResetEvent and a concealed System.Windows.Forms.Timer to ferry the polling notice across threads. Cleanup code is omitted for the sake of brevity. Requiring the use of IDisposable for explicit cleanup would be recommended in a production version of this class.

ManualResetEvent @ MSDN

public class PollingListener2
{
    System.Timers.Timer timer = new System.Timers.Timer(1000);
    public event EventHandler<EventArgs> Polled;

    System.Windows.Forms.Timer formsTimer;
    public System.Threading.ManualResetEvent pollNotice;

    public PollingListener2()
    {
        pollNotice = new System.Threading.ManualResetEvent(false);
        formsTimer = new System.Windows.Forms.Timer();
        formsTimer.Interval = 100;
        formsTimer.Tick += new EventHandler(formsTimer_Tick);
        formsTimer.Start();
        timer.Elapsed += new System.Timers.ElapsedEventHandler(PollNow);
        timer.Start();
    }

    void formsTimer_Tick(object sender, EventArgs e)
    {
        if (pollNotice.WaitOne(0))
        {
            pollNotice.Reset();
            var temp = Polled;
            if (temp != null)
            {
                Polled(this, new EventArgs());
            }
        }
    }

    void PollNow(object sender, EventArgs e)
    {
        pollNotice.Set();
    }
}

This has some precedent in the distant Win32 past where some people would use hidden windows and the like to maintain one foot in the other thread without requiring the consumer to make any significant changes to their code (sometimes no changes are necessary).


Original:

You could add a member variable on your helper class of type Control or Form and use that as the scope for a BeginInvoke() / Invoke() call on your event dispatch.

Here's a copy of your sample class, modified to behave in this manner.

public class PollingListener
{
    System.Timers.Timer timer = new System.Timers.Timer(1000);
    public event EventHandler<EventArgs> Polled;

    public PollingListener(System.Windows.Forms.Control consumer)
    {
        timer.Elapsed += new System.Timers.ElapsedEventHandler(PollNow);
        timer.Start();
        consumerContext = consumer;
    }

    System.Windows.Forms.Control consumerContext;

    void PollNow(object sender, EventArgs e)
    {
        var temp = Polled;
        if ((temp != null) && (null != consumerContext))
        {
            consumerContext.BeginInvoke(new Action(() =>
                {
                    Polled(this, new EventArgs());
                }));
        }
    }
}

Here's a sample that shows this in action. Run this in debug mode and look at your output to verify that it is working as expected.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        listener = new PollingListener(this);
    }

    PollingListener listener;

    private void Form1_Load(object sender, EventArgs e)
    {
        listener.Polled += new EventHandler<EventArgs>(listener_Poll);
    }

    void listener_Poll(object sender, EventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("ding.");
    }
}
meklarian
  • 6,595
  • 1
  • 23
  • 49
  • I have used that before and surely this works, but I was looking for a solution that didn't require explicitly providing my object a reference to my form/control. I tried to express that by mentioning not changing my method signatures to accommodate, but I never mentioned properties. – deepee1 Mar 01 '12 at 23:54
0

If the processing work inside your PollNow is fairly small then you do not need to perform it on a separate thread. If WinForms use Timer, in WPF you use DispatchTimer and then you are performing the test on the same thread as the UI and there is no cross-thread issue.

Phil Wright
  • 22,580
  • 14
  • 83
  • 137
0

This SO question prompted this comment:

I think this excerpt is enlightening: "Unlike the System.Windows.Forms.Timer, the System.Timers.Timer class will, by default, call your timer event handler on a worker thread obtained from the common language runtime (CLR) thread pool. [...] The System.Timers.Timer class provides an easy way to deal with this dilemma—it exposes a public SynchronizingObject property. Setting this property to an instance of a Windows Form (or a control on a Windows Form) will ensure that the code in your Elapsed event handler runs on the same thread on which the SynchronizingObject was instantiated."

And System.Times.Timer doc says of SynchronizingObject:

Gets or sets the object used to marshal event-handler calls that are issued when an interval has elapsed.

Both of which implie that if you pass a control created on the UI thread as the sync object then the timer will effectively marshal the timer event calls to the UI thread.

Community
  • 1
  • 1
Ricibob
  • 7,505
  • 5
  • 46
  • 65