简体   繁体   中英

Parallel.Invoke wait if tasks are busy

Can someone help me with the following code please. in the line:

Parallel.Invoke(parallelOptions, () => dosomething(message));

I want to invoke up to 5 parallel tasks (if there are 5 busy, wait for the next available, then start it... if only 4 busy, start a 5th, etc)

    private AutoResetEvent autoResetEvent1 = new AutoResetEvent(false);
    private ParallelOptions parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 5 };

    private void threadProc()
    {
        queue.ReceiveCompleted += MyReceiveCompleted1;
        Debug.WriteLine("about to start loop");
        while (!shutdown)
        {
            queue.BeginReceive();
            autoResetEvent1.WaitOne();
        }
        queue.ReceiveCompleted -= MyReceiveCompleted1;
        queue.Dispose();
        Debug.WriteLine("we are done");
    }

    private void MyReceiveCompleted1(object sender, ReceiveCompletedEventArgs e)
    {
        var message = queue.EndReceive(e.AsyncResult);
        Debug.WriteLine("number of max tasks: " + parallelOptions.MaxDegreeOfParallelism);
        Parallel.Invoke(parallelOptions, () => dosomething(message));
        autoResetEvent1.Set();
    }

    private void dosomething(Message message)
    {
        //dummy body
        var i = 0;
        while (true)
        {
            Thread.Sleep(TimeSpan.FromSeconds(1));
            i++;
            if (i == 5 || i == 10 || i == 15) Debug.WriteLine("loop number: " + i + " on thread: " + Thread.CurrentThread.ManagedThreadId);
            if (i == 15)
                break;
        }
        Debug.WriteLine("finished task");
    }

THE RESULTS I GET NOW:

1) with dosomething() as you see it above, i get only one at a time (it waits)

2) with dosomething() changed to below, i get none stops x number of tasks (not limited or obeying MaxDegreeOfParallelism

    private async Task dosomething(Message message)
    {
        //dummy body
        var i = 0;
        while (true)
        {
            await Task.Delay(TimeSpan.FromSeconds(1));
            i++;
            if (i == 5 || i == 10 || i == 15) Debug.WriteLine("loop number: " + i + " on thread: " + Thread.CurrentThread.ManagedThreadId);
            if (i == 15)
                break;
        }
        Debug.WriteLine("finished task");
    }

What am i doing wrong to get what i want accomplished?

THE OUTCOME I WANT:

in "MyReceiveCompleted", i want to make sure only 5 simultaneous tasks are processing messages, if there are 5 busy ones, wait for one to become available.

This line of code:

Parallel.Invoke(parallelOptions, () => dosomething(message));

is telling the TPL to start a new parallel operation with only one thing to do . So, the "max parallelism" option of 5 is kind of meaningless, since there will only be one thing to do. autoResetEvent1 ensures that there will only be one parallel operation at a time, and each parallel operation only has one thing to do, so the observed behavior of only one thing running at a time is entirely expected.

When you change the delegate to be asynchronous, what is actually happening is that the MaxDegreeOfParallelism is only applied to the synchronous portion of the method. So once it hits its first await , it "leaves" the parallel operation and is no longer constrained by it.

The core problem is that Parallel works best when the number of operations is known ahead of time - which your code doesn't know; it's just reading them from a queue and processing them as they arrive. As someone commented, you could solve this using dynamic task parallelism with a TaskScheduler that limits concurrency.

However, the simplest solution is probably TPL Dataflow. You can create an ActionBlock<T> with the appropriate throttling option and send messages to it as they arrive:

private ActionBlock<string> block = new ActionBlock<string>(
    message => dosomething(message),
    new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 5 });

private void MyReceiveCompleted1(object sender, ReceiveCompletedEventArgs e)
{
  var message = queue.EndReceive(e.AsyncResult);
  block.Post(message);
  autoResetEvent1.Set();
}

One nice aspect of TPL Dataflow is that it also understands asynchronous methods, and will interpret MaxDegreeOfParallelism as a maximum degree of concurrency.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM