سؤال

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?

هل كانت مفيدة؟

المحلول

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().

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top