Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RegisterWaitForSingleObject undefined behavior with ManualResetEvent

In one of my projects, I need to schedule a task to be done after some specified interval in the background thread. I use the ThreadPool.RegisterWaitForSingleObject mechanism to do that. According to the documentation, the first argument could be a WaitHandle which has many sub classes. At first I preferred ManualResetEvent; I use this event to signal to fulfill the schedule and exit. Here is my code:

public class ScheduleTester
{
    private long m_LastTicks;
    private RegisteredWaitHandle m_RegisterWaitHandle;
    public ManualResetEvent Event;

    public ScheduleTester()
    {
        Event = new ManualResetEvent(false);
        m_RegisterWaitHandle = ThreadPool.RegisterWaitForSingleObject(Event, new WaitOrTimerCallback(WaitOrTimerCallback), null, 500, false);
        m_LastTicks = DateTime.Now.Ticks;
    }

    private void WaitOrTimerCallback(object state, bool timedOut)
    {
        long ticks = DateTime.Now.Ticks;

        if (timedOut)
        {
            Console.WriteLine(string.Format("Timeout : {0} ... Do scheduled job takes 1 seconds long, Thread ID : {1}", (ticks - m_LastTicks) / 10000f, Thread.CurrentThread.GetHashCode()));                                
        }
        else
        {
            Console.WriteLine(string.Format("Signaled....Unregister , Thread ID : {0}", Thread.CurrentThread.GetHashCode()));

            if (null != m_RegisterWaitHandle)
                m_RegisterWaitHandle.Unregister(null);
        }

        m_LastTicks = ticks;
    }
}

static void Main()
{
    ScheduleTester waitFor = new ScheduleTester();
    Console.ReadKey();
    waitFor.Event.Set();
    Console.ReadKey();
}

What I expected is to see many lines which notify a scheduled job has done until I press a key to signal the event and a single line noticed the scheduled fulfilled. But what I see is: output1

When the ManualResetEvent is replaced with AutoResetEvent every thing is good. output2

like image 448
AhmadAli Avatar asked Mar 13 '23 04:03

AhmadAli


1 Answers

Yes, that is not exactly very intuitive behavior. You can easily see the underlying problem from the debugger's Debug > Windows > Threads window. You'll see a large number of tp threads all executing your WaitOrTimerCallback() method.

It is up to you to stop this from happening. The executeOnlyOnce parameter of RWFSO is very, very important. You now pass false, that makes it important that the sync object you wait for gets signaled only once. Like AutoResetEvent does.

If you use a ManualResetEvent then you must pass true. And tweak your callback to:

private void WaitOrTimerCallback(object state, bool timedOut) {
    long ticks = DateTime.Now.Ticks;
    if (timedOut) {
        Console.WriteLine(string.Format("Timeout : {0} ... Do scheduled job takes 1 seconds long, Thread ID : {1}", (ticks - m_LastTicks) / 10000f, Thread.CurrentThread.ManagedThreadId));
        ThreadPool.RegisterWaitForSingleObject(Event, new WaitOrTimerCallback(WaitOrTimerCallback), null, 500, true);
    }
    else {
        Console.WriteLine(string.Format("Signaled....Unregister , Thread ID : {0}", Thread.CurrentThread.ManagedThreadId));
        m_RegisterWaitHandle.Unregister(null);
    }
    m_LastTicks = ticks;
}

You'll now see it detecting timeouts at the wait interval. Not sure if that was intended.

like image 157
Hans Passant Avatar answered May 04 '23 10:05

Hans Passant