开发者

Return to zero CountdownEvent

I'm trying to use a CountdownE开发者_高级运维vent to only allow threads to continue when the event's count is zero, however I would like the initial count to be zero. In effect I'd like a return to zero behaviour whereby the event is signalled whenever the count is zero and threads are made to wait whenever it's greater than zero.

I can initialize a Countdown event with 0 initial count but when I try to add to the count I get InvalidOperationException "CountdownEvent_Increment_AlreadyZero" .

Is there an alternative class or another way that I can use Countdown event in order to avoid this limitation?


Edit

public void Dispatch()
{
    using (var ev = new CountdownEvent(1))
    {
        foreach (var task in <collection_of_tasks_to_start>)
        {
            ev.AddCount();
            // start *task* here. Don't forget to pass *ev* to it!
        }

        ev.Signal();
        ev.Wait();
    }
}

// task code
void Handler(CountdownEvent ev)
{
    try
    {
        // do task logic
    }
    finally
    {
        ev.Signal();
    }
}

Why and how this works?

  1. If you haven't spawned any tasks
    • nothing will happen and Dispatch will just finish normally.
    • ev.Signal makes sure that once ev.Wait is called, initial value of the counter is 0 and Wait will not block execution
  2. If you spawned at least one task
    • we have two cases to consider: fast- and slow-running tasks and understand how they affect execution at point where ev.Wait() is called.
      • fast tasks, i.e. tasks that will complete before we reach the end.
        • at the ev.Wait() the situation is equivalent to point 1.
      • slow tasks, i.e. tasks that haven't completed before we reach the end.
        • at the ev.Wait() the counter is not equal to 0 anymore, because of all the ev.AddCount() executed. Thus, execution will hold.
        • once all running tasks complete (and have corresponding ev.Signal() lines executed) the counter drops to 0 and execution resumes exiting the routine

Original answer

You wrote:

I am performing an operation that will create an unknown number of child operations (not tasks or threads)

So what are they? You should do something like this:

CountdownEvent ev;  
public void foo() {
    ev = new CountdownEvent(1);
    foreach ( <task in tasks_to_start> ) {
         ev.AddCount();
         // enter code here which starts your task
    }
    ev.Signal();
    ev.Wait();
}

public static void youtTask(CountdownEvent ev) {
    // some work
    // ...
    // after all is done
    ev.Signal();
}


If you can use .NET 4.0 or Reactive Extensions for .NET 3.5 (which has a backport of the .NET 4 TPL features), you might check out the Barrier class. It allows you to coordinate multiple parallel tasks so that they do not continue until all participants in the barrier have signaled their arrival. It should also meet your requirement to have participants appear and disappear during the course of processing.


So in essence you need an "on/off switch", not a synchronization object that can be set up with an arbitrary countdown. CountdownEvent is not appropriate for such cases.

Why don't you just use a Semaphore with an initial count of one?


Will this work for you? http://msdn.microsoft.com/en-us/library/dd384749.aspx

Edit
Sorry that was vague. Using SOReader's answer where you have a countdown event in each parent starting at 1 - then using TryAddCount in children increment and later decrement parents back to 1, then in the parent decrement from 1 to zero when children are done, and finally decrement the count in the parent of the parent thread. So a tree-like series of countdownevents.

I'm not experienced with multithreading but at first glance that's what I would try.


Your question seems like is a common tree walk forking technique. Each time you would have recursed, you instead kick off another concurrent operation (enqueue it to a thread pool, etc). But you need to wait for all the subbranches to finish at the end. Simply add 1 to the countdown event for each sub-operation you kick off, and signal it at the end of each sub-operation. It is safe to do as long as you arrange the algorithm so it won't signal until it adds for each child operation.

I should add that you don't need to know the count up front, just make it 1 at the root, and each time you fork off to a child, add 1, then signal at the end of each one, and it will dynamically handle any tree with no up-front cost.

CountdownEvent has a method Add which lets you increase the count in flight.

Does that make sense? I may be way off track of what you are trying to accomplish.

However, if you really want a CountdownEvent that behaves the way you specified, it is quite easy to wrap a couple of interlocked operations in a class to do what you say.

However, CountdownEvent is built to be feather-weight, it's almost free if nobody waits before it's signalled. In the expensive case, it is optimal, no matter how many tasks (etc) it will only have to make one kernel transition to signal and one to wait, worst case.

To implement what you propose would require a synchronization around the signalling and resetting of the event. The countdown event relies on one simple principle, only the transition from nonzero to zero in a Signal call can possibly signal the event. There is no race, since it is not possible for more than one thread to change the value at one time (it's interlocked) so it is only possible for one thread to try to signal the event object (that awakens the other waiting thread). Perfect.

However, if you have multiple threads setting and resetting it, you need to sync around the set and reset, since the count might jitter a couple of times and multiple threads would all simultaneously be trying to set or reset the event. (Setting, resetting, and waiting for an event are all expensive because they all have to make a kernel transition and cause context switches). It wouldn't work unless you synchronized around something to protect the set/reset transitions. If they added this to CountdownEvent it would no longer be nearly optimal, it would be significantly more expensive.


How about semaphores: http://msdn.microsoft.com/en-us/library/system.threading.semaphore.aspx

Edit: The following post discusses why what you describe is not recommended and also suggests a workaround: http://social.msdn.microsoft.com/Forums/en/parallelextensions/thread/aa49f92c-01a8-4901-9846-91bc1587f3ae


You can use a Queue object to add "work" into and take out again.

Only when the Queue is empty you move on.

But yeah, we're gonna need specifics here...


I run into the same issue, but in the context of Barrier.

Actually If you think about it, CountdownEvent is a one phase Barrier.

So in order to avoide the limitation of:

I can initialize a Countdown event with 0 initial count but when I try to add to the count I get InvalidOperationException "CountdownEvent_Increment_AlreadyZero".

you may just move on to Barrier and use its AddParticipant or RemoveParticipan methods.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜