C# 可取消的 WaitAll WaitHandle

6
我有以下代码,目的是等待所有给定的等待句柄,但可以通过特定的等待句柄取消:
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;
}

这仅适用于手动重置事件。当使用自动重置事件时,WaitAny会重置所有已发出的事件,但仅返回第一个已发出的事件(根据MSDN文档)。

有没有什么好的方法可以在不轮询的情况下以正确的方式使用自动重置事件来完成此操作?


尝试使用其中一个重载方法。在进入do-while之前尝试创建数组,也许你会有新的想法。 - bash.d
如果取消事件被触发,我无法理解这将如何工作? - LukeHennerley
1
如果取消事件被触发,等待所有给定的 waitHandles 将被取消。 - Harry13
@Luke,这是因为取消句柄位于索引0,而while()确保发出信号的事件不在索引0(handleIdx != 0)。 - Matthew Watson
是的,您说得对,在不同线程中使用AutoReset事件进行多个等待操作是行不通的,即使您使用内置的.NET功能。 - Harry13
显示剩余3条评论
1个回答

1
我认为你的方法应该按原样正常工作。
我相信WaitHandle.WaitAny()使用了Windows API函数WaitForMultipleObjects(), 其文档如下:

只有导致函数返回的对象或对象的修改才会发生。

如果是真的,那么你的代码应该可以工作。
我写了一个测试程序。它创建了一堆AutoResetEvents,并在调用CancelableWaitAll()之前设置了其中一半。然后它启动一个线程,在等待5秒钟后设置另一半AutoResetEvents。在启动该线程后,主线程立即调用CancelableWaitAll()。
如果WaitAny()实际上重置了任何除了返回其索引的事件之外的自动重置事件,那么CancelableWaitAll()将永远不会返回。
因为它确实返回了(当然要经过5秒),我断言你的代码可以使用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;
        }
    }
}

很遗憾,我无法证明WaitHandle.WaitAll()使用了WaitForMultipleObjects()。但是,如果没有使用,您可以通过使用WaitHandle.SafeWaitHandle来获取操作系统事件句柄并使用P/Invoke调用WaitForMultipleObjects()来自行调用它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接