C# WaitHandle cancelable WaitAll
Asked Answered
P

1

6

I have the following code which has the goal to wait for all given wait handles but is cancellable by a specific wait handle:

public static bool CancelableWaitAll(WaitHandle[] waitHandles, WaitHandle cancelWaitHandle)
{
    var waitHandleList = new List<WaitHandle>();
    waitHandleList.Add(cancelWaitHandle);
    waitHandleList.AddRange(waitHandles);
    int handleIdx;
    do
    {
        handleIdx = WaitHandle.WaitAny(waitHandleList.ToArray());
        waitHandleList.RemoveAt(handleIdx);
    }
    while (waitHandleList.Count > 1 && handleIdx != 0);
    return handleIdx != 0;
}

This works only for ManualReset events. When using AutoReset events WaitAny resets all signalled events but returns only the first signalled (according MSDN).

Any ideas how to get this done with AutoReset events in a proper way without polling?

Piston answered 25/2, 2013 at 14:38 Comment(8)
Try to use one of the overloaded methods. And try to create the array before entering do-while, maybe you get new insights.Pietra
I can't understand how this is going to work if the cancel event is raised?Bagpipes
If the cancellation event is raised the waiting for all given waitHandles is cancelledPiston
@Luke, and that's because the cancel handle is at index 0 and the while() is ensuring that the signalled event isn't at index 0 (handleIdx != 0)Reachmedown
@OP: Have you actually tried this with autoreset events? According to the documentation for WaitForMultipleObjects (which I think this uses) "Modification occurs only for the object or objects whose signaled state caused the function to return." See: "msdn.microsoft.com/en-us/library/windows/desktop/…Reachmedown
With AutoReset events you've got a major problem here. You stop waiting to check if index 0 was the culprit. If it isn't, you wait again. The problem is an event could get raised while you're not waiting--which could result in an infinite loop.Lavation
Wouldn't the autoreset event remain signalled until it was successfully waited on? If any thread other than the one calling CancelableWaitAll() was also waiting on any of the autoreset events, that would be a total disaster. But consider my test code below. You can add a sleep of 1000ms just after waitHandleList.RemoveAt(handleIdx) and it still works ok.Reachmedown
Yes you are right, using AutoReset events for multiple Wait operations in different threads is not working, event if you use the builtin .NET functionalityPiston
R
1

I think that your method should work correctly as-written.

I believe that WaitHandle.WaitAny() uses the Windows API function WaitForMultipleObjects(), the documentation for which says:

Modification occurs only for the object or objects whose signaled state caused the function to return.

If true, it means that your code should work.

I wrote a test program. It creates a load of AutoResetEvents and sets half of them before calling CancelableWaitAll(). Then it starts a thread that waits 5 seconds before setting the other half of the AutoResetEvents. Immediately after starting that thread, the main thread calls CancelableWaitAll().

If the WaitAny() actually reset any of the autoreset events other than the one whose index was returned, the CancelableWaitAll() would never return.

Because it does return (after 5 seconds of course), I'm asserting that your code works with AutoResetEvents:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Demo
{
    public static class Program
    {
        private static void Main(string[] args)
        {
            AutoResetEvent[] events = new AutoResetEvent[32];

            for (int i = 0; i < events.Length; ++i)
            {
                events[i] = new AutoResetEvent(false);
            }

            // Set the first 16 auto reset events before calling CancelableWaitAll().

            for (int i = 0; i < 16; ++i)
            {
                events[i].Set();
            }

            // Start a thread that waits five seconds and then sets the rest of the events.

            Task.Factory.StartNew(() => setEvents(events));

            Console.WriteLine("Waiting for all events to be set.");

            ManualResetEvent stopper = new ManualResetEvent(false);
            CancelableWaitAll(events, stopper);

            Console.WriteLine("Waited.");
        }

        private static void setEvents(AutoResetEvent[] events)
        {
            Thread.Sleep(5000);

            for (int i = 16; i < events.Length; ++i)
            {
                events[i].Set();
            }
        }

        public static bool CancelableWaitAll(WaitHandle[] waitHandles, WaitHandle cancelWaitHandle)
        {
            var waitHandleList = new List<WaitHandle>();
            waitHandleList.Add(cancelWaitHandle);
            waitHandleList.AddRange(waitHandles);
            int handleIdx;
            do
            {
                handleIdx = WaitHandle.WaitAny(waitHandleList.ToArray());
                waitHandleList.RemoveAt(handleIdx);
            }
            while (waitHandleList.Count > 1 && handleIdx != 0);
            return handleIdx != 0;
        }
    }
}

Unfortunately, I can't prove that WaitHandle.WaitAll() uses WaitForMultipleObjects(). However, if it didn't, you could call it yourself by using WaitHandle.SafeWaitHandle to get at the OS event handles and use P/Invoke to call WaitForMultipleObjects().

Reachmedown answered 25/2, 2013 at 15:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.