代码之家  ›  专栏  ›  技术社区  ›  James

WaitHandle.WaitAll 64句柄限制的解决方法?

  •  46
  • James  · 技术社区  · 16 年前

    我的应用程序通过 ThreadPool.QueueUserWorkItem 我通过多个 ManualResetEvent WaitHandle.WaitAll 方法阻止我的应用程序关闭,直到这些线程完成。

    我以前从来没有遇到过任何问题,但是,由于我的应用程序的负载越来越大,即创建的线程越来越多,我现在开始遇到这个异常:

    WaitHandles must be less than or equal to 64 - missing documentation

    最好的替代方案是什么?

    代码段

    List<AutoResetEvent> events = new List<AutoResetEvent>();
    
    // multiple instances of...
    var evt = new AutoResetEvent(false);
    events.Add(evt);
    ThreadPool.QueueUserWorkItem(delegate
    {
        // do work
        evt.Set();
    });
    
    ...
    WaitHandle.WaitAll(events.ToArray());
    

    变通办法

    int threadCount = 0;
    ManualResetEvent finished = new ManualResetEvent(false);
    
    ...
    Interlocked.Increment(ref threadCount);
    ThreadPool.QueueUserWorkItem(delegate
    {
        try
        {
             // do work
        }
        finally
        {
            if (Interlocked.Decrement(ref threadCount) == 0)
            {
                 finished.Set();
            }
        }
    });
    
    ...
    finished.WaitOne();
    
    8 回复  |  直到 16 年前
        1
  •  47
  •   dtb    16 年前

    创建跟踪正在运行的任务数的变量:

    int numberOfTasks = 100;
    

    ManualResetEvent signal = new ManualResetEvent(false);
    

    在任务完成时减少任务数:

    if (Interlocked.Decrement(ref numberOftasks) == 0)
    {
    

    如果没有剩余任务,则设置信号:

        signal.Set();
    }
    

    同时,在其他地方等待信号设置:

    signal.WaitOne();
    
        2
  •  43
  •   casperOne    11 年前

    CountdownEvent class . 它避免了必须自己处理递增和递减的需要:

    int tasks = <however many tasks you're performing>;
    
    // Dispose when done.
    using (var e = new CountdownEvent(tasks))
    {
        // Queue work.
        ThreadPool.QueueUserWorkItem(() => {
            // Do work
            ...
    
            // Signal when done.
            e.Signal();
        });
    
        // Wait till the countdown reaches zero.
        e.Wait();
    }
    

    Task class ,就像这样:

    // The source of your work items, create a sequence of Task instances.
    Task[] tasks = Enumerable.Range(0, 100).Select(i =>
        // Create task here.
        Task.Factory.StartNew(() => {
            // Do work.
        }
    
        // No signalling, no anything.
    ).ToArray();
    
    // Wait on all the tasks.
    Task.WaitAll(tasks);
    

    任务 上课和打电话给 WaitAll 更干净,IMO,因为您在代码中编织的线程原语更少(注意,没有等待句柄);你不需要设置一个计数器,处理递增/递减,你只需要设置你的任务,然后等待它们。这使得代码在 什么 怎样

    任务 通过调用 static Run method on the Task class :

    // The source of your work items, create a sequence of Task instances.
    Task[] tasks = Enumerable.Range(0, 100).Select(i =>
        // Create task here.
        Task.Run(() => {
            // Do work.
        })
    
        // No signalling, no anything.
    ).ToArray();
    
    // Wait on all the tasks.
    Tasks.WaitAll(tasks);
    

    TPL DataFlow library (它在房间里。) System ActionBlock<TInput> ,就像这样:

    // Create the action block.  Since there's not a non-generic
    // version, make it object, and pass null to signal, or
    // make T the type that takes the input to the action
    // and pass that.
    var actionBlock = new ActionBlock<object>(o => {
        // Do work.
    });
    
    // Post 100 times.
    foreach (int i in Enumerable.Range(0, 100)) actionBlock.Post(null);
    
    // Signal complete, this doesn't actually stop
    // the block, but says that everything is done when the currently
    // posted items are completed.
    actionBlock.Complete();
    
    // Wait for everything to complete, the Completion property
    // exposes a Task which can be waited on.
    actionBlock.Completion.Wait();
    

    请注意 操作块<t输入> 默认情况下,一次处理一个项,因此如果希望它一次处理多个操作,则必须通过传递 ExecutionDataflowBlockOptions MaxDegreeOfParallelism property

    var actionBlock = new ActionBlock<object>(o => {
        // Do work.
    }, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });
    

    MaxDegreeOfParallelsim 属性到 DataFlowBlockOptions.Unbounded

    var actionBlock = new ActionBlock<object>(o => {
        // Do work.
    }, new ExecutionDataflowBlockOptions { 
        MaxDegreeOfParallelism = DataFlowBlockOptions.Unbounded
    });
    

    关键是,您对 怎样

    当然,如果您有一系列要传递到 ISourceBlock<TOutput> 实现以满足 操作块<t输入>

    // The buffer block.
    var buffer = new BufferBlock<int>();
    
    // Create the action block.  Since there's not a non-generic
    // version, make it object, and pass null to signal, or
    // make T the type that takes the input to the action
    // and pass that.
    var actionBlock = new ActionBlock<int>(o => {
        // Do work.
    });
    
    // Link the action block to the buffer block.
    // NOTE: An IDisposable is returned here, you might want to dispose
    // of it, although not totally necessary if everything works, but
    // still, good housekeeping.
    using (link = buffer.LinkTo(actionBlock, 
        // Want to propagate completion state to the action block.
        new DataflowLinkOptions {
            PropagateCompletion = true,
        },
        // Can filter on items flowing through if you want.
        i => true)
    { 
        // Post 100 times to the *buffer*
        foreach (int i in Enumerable.Range(0, 100)) buffer.Post(i);
    
        // Signal complete, this doesn't actually stop
        // the block, but says that everything is done when the currently
        // posted items are completed.
        actionBlock.Complete();
    
        // Wait for everything to complete, the Completion property
        // exposes a Task which can be waited on.
        actionBlock.Completion.Wait();
    }
    

    根据您需要做什么,TPL数据流库将成为 全部的 任务联系在一起,它允许你非常具体的

        3
  •  18
  •   Brian Gideon    16 年前

    您的解决方法不正确。原因是 Set 和 WaitOne threadCount 归零 之前 全部的 工作项。解决方法很简单。将排队线程视为工作项本身。初始化 线程数

    int threadCount = 1;
    ManualResetEvent finished = new ManualResetEvent(false);
    ...
    Interlocked.Increment(ref threadCount); 
    ThreadPool.QueueUserWorkItem(delegate 
    { 
        try 
        { 
             // do work 
        } 
        finally 
        { 
            if (Interlocked.Decrement(ref threadCount) == 0) 
            { 
                 finished.Set(); 
            } 
        } 
    }); 
    ... 
    if (Interlocked.Decrement(ref threadCount) == 0)
    {
      finished.Set();
    }
    finished.WaitOne(); 
    

    作为个人喜好,我喜欢使用 CountdownEvent 请全班同学帮我数数。

    var finished = new CountdownEvent(1);
    ...
    finished.AddCount();
    ThreadPool.QueueUserWorkItem(delegate 
    { 
        try 
        { 
             // do work 
        } 
        finally 
        { 
          finished.Signal();
        } 
    }); 
    ... 
    finished.Signal();
    finished.Wait(); 
    
        4
  •  6
  •   ChaosPandion    16 年前

    添加到dtb的答案中,您可以将其包装成一个很好的简单类。

    public class Countdown : IDisposable
    {
        private readonly ManualResetEvent done;
        private readonly int total;
        private long current;
    
        public Countdown(int total)
        {
            this.total = total;
            current = total;
            done = new ManualResetEvent(false);
        }
    
        public void Signal()
        {
            if (Interlocked.Decrement(ref current) == 0)
            {
                done.Set();
            }
        }
    
        public void Wait()
        {
            done.WaitOne();
        }
    
        public void Dispose()
        {
            ((IDisposable)done).Dispose();
        }
    }
    
        5
  •  0
  •   user2918406    10 年前

    当我们想要回拨电话时,增加dtb的答案。

    using System;
    using System.Runtime.Remoting.Messaging;
    using System.Threading;
    
    class Program
    {
        static void Main(string[] args)
        {
            Main m = new Main();
            m.TestMRE();
            Console.ReadKey();
    
        }
    }
    
    class Main
    {
        CalHandler handler = new CalHandler();
        int numberofTasks =0;
        public void TestMRE()
        {
    
            for (int j = 0; j <= 3; j++)
            {
                Console.WriteLine("Outer Loop is :" + j.ToString());
                ManualResetEvent signal = new ManualResetEvent(false);
                numberofTasks = 4;
                for (int i = 0; i <= 3; i++)
                {
                    CalHandler.count caller = new CalHandler.count(handler.messageHandler);
                    caller.BeginInvoke(i, new AsyncCallback(NumberCallback),signal);
                }
                signal.WaitOne();
            }
    
        }
    
        private void NumberCallback(IAsyncResult result)
        {
            AsyncResult asyncResult = (AsyncResult)result;
    
            CalHandler.count caller = (CalHandler.count)asyncResult.AsyncDelegate;
    
            int num = caller.EndInvoke(asyncResult);
    
            Console.WriteLine("Number is :"+ num.ToString());
    
            ManualResetEvent mre = (ManualResetEvent)asyncResult.AsyncState;
            if (Interlocked.Decrement(ref numberofTasks) == 0)
            {
                mre.Set();
            }
        }
    
    }
    public class CalHandler
    {
        public delegate int count(int number);
    
        public int messageHandler ( int number )
        {
            return number;
        }
    
    }
    
        6
  •  0
  •   TOL    9 年前
    protected void WaitAllExt(WaitHandle[] waitHandles)
    {
        //workaround for limitation of WaitHandle.WaitAll by <=64 wait handles
        const int waitAllArrayLimit = 64;
        var prevEndInd = -1;
        while (prevEndInd < waitHandles.Length - 1)
        {
            var stInd = prevEndInd + 1;
            var eInd = stInd + waitAllArrayLimit - 1;
            if (eInd > waitHandles.Length - 1)
            {
                eInd = waitHandles.Length - 1;
            }
            prevEndInd = eInd;
    
            //do wait
            var whSubarray = waitHandles.Skip(stInd).Take(eInd - stInd + 1).ToArray();
            WaitHandle.WaitAll(whSubarray);
        }
    
    }
    
        7
  •  0
  •   Luiz Paulo    9 年前

    我通过简单地分页等待的事件数量而不损失太多性能来解决这个问题,而且它在生产环境中工作得非常好。遵循代码:

            var events = new List<ManualResetEvent>();
    
            // code omited
    
            var newEvent = new ManualResetEvent(false);
            events.Add(newEvent);
            ThreadPool.QueueUserWorkItem(c => {
    
                //thread code
                newEvent.Set();
            });
    
            // code omited
    
            var wait = true;
            while (wait)
            {
                WaitHandle.WaitAll(events.Take(60).ToArray());
                events.RemoveRange(0, events.Count > 59 ? 60 : events.Count);
                wait = events.Any();
    
            }
    
        8
  •  0
  •   IlGala    7 年前

    这是另一个解决方案。这里的“事件”是ManualResetEvent的列表。列表的大小可以大于64(最大事件数)。

    int len = events.Count;
    if (len <= MAX_EVENTS_NO)
        {
            WaitHandle.WaitAll(events.ToArray());
        } else {
            int start = 0;
            int num = MAX_EVENTS_NO;
            while (true)
                {
                    if(start + num > len)
                    {
                       num = len - start;
                    }
                    List<ManualResetEvent> sublist = events.GetRange(start, num);
                    WaitHandle.WaitAll(sublist.ToArray());
                    start += num;
                    if (start >= len)
                       break;
               }
       }
    
    
        9
  •  -2
  •   monotosh    12 年前

    WindowsXPSP3最多支持两个WaitHandle。对于超过2个WaitHandles的情况,应用程序会提前终止。