0

I have a application, which to make concurrently task running. Here we set MaxDegreeOfParallelism=4, which means at any time at most 4 tasks running concurrently. In this case, I only have 4 channels available. Otherwise an exception

Could not get Channel

will be thrown.

Each task will have an instance of OutboundDial, so at most it will be 4 instances.

 public class OutboundDial
 {
    private ChannelResource m_ChannelResource;
    private VoiceResource m_VoiceResource;
    private TelephonyServer m_TelephonyServer;
    private AppointmentReminderResult m_Result = new AppointmentReminderResult();

    public OutboundDial(TelephonyServer telephonyServer)
    {
        m_TelephonyServer = telephonyServer;
    }

    internal void RunScript(AppointmentReminder callData) 
    {
        try
        {
            try
            {
                m_ChannelResource = m_TelephonyServer.GetChannel();
                m_VoiceResource = m_ChannelResource.VoiceResource;
            }
            catch (Exception ex)
            {
                Console.WriteLine("Could not get channel: {0}",ex.StackTrace);
                return;
            }
            // a long running process of I/O bound operation

The producer-consumer queue is

 public static BufferBlock<AppointmentReminder> m_Queue =
        new BufferBlock<AppointmentReminder>(new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = 4});

BufferBlock is a TPL class. The TelephontServer was initialized at the very beginning.

public static TelephonyServer ts;
ts = new TelephonyServer(sIpaddress, "username", "password");

In the consumer part, we have:

static async  Task Consumer()
{
    try
    {
        while (await m_Queue.OutputAvailableAsync())
        {
            m_Queue.TryReceive(4, ts); // MaxDegreeOfParallelism = 4
        }
    }

TryReceive is an extension method.

 public static void TryReceive<T>(this BufferBlock<T> bufferBlock, int count, TelephonyServer ts) where T : AppointmentReminder
    {
        try
        {
            for (var i = 0; i < count; i++)
            {
                T item;
                if (bufferBlock.TryReceive(out item))
                {

                    Task t = Task.Run(() =>
                        {
                            OutboundDial d = new OutboundDial(ts);
                            d.RunScript<T>((T)item);
                        });
                }
                else
                {
                    break;
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.StackTrace);
        }
    }

My question: I added 10 items to the queue in the producer part and I set a breaking point at the constructor.

I found the code run 10 times in the constructor then 10 times RunScript, which indicated 10 tasks run together rather than 4. But I only want 4(MaxDegreeOfParallelism). Therefore I did't have enough channels available, an exception was thrown.

Why in my extension method the concurrent running was not working?

  • 3
    Try to minimize the problem first, please. And if some detail about the exception would be helpful. – silvesthu Oct 13 '14 at 13:44
  • @silvesthu, hold on. I will edit the problem to make it small. –  Oct 13 '14 at 14:00
  • By the doc here, I don't think MaxDegreeOfParallelism works as you thought. It also decide how many tasks run together, not how many times total. http://msdn.microsoft.com/library/system.threading.tasks.paralleloptions.maxdegreeofparallelism(v=vs.110).aspx – silvesthu Oct 13 '14 at 14:35
  • @silvesthu, you are right. I want 4 tasks run together. But why it was 10? –  Oct 13 '14 at 14:40

2 Answers2

1
  1. BufferBlock doesn't really execute anything, so it doesn't make sense to specify its MaxDegreeOfParallelism. It works only because ExecutionDataflowBlockOptions inherits from DataflowBlockOptions, which is what the BufferBlock constructor expects.

  2. Your Consumer() does this: take up to 4 items and execute them, take up to 4 items and execute them, take up to 4 items and execute them, etc. Since your never wait for those executions to complete, you're not actually limiting the degree of parallelism this way.

  3. If you want to limit the degree of parallelism to 4, you could use ActionBlock, instead of your combination of BufferBlock and Consumer():

    new ActionBlock<AppointmentReminder>(
        reminder => new OutboundDial(ts).RunScript(reminder),
        new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4});
    

    What this does is to execute the lambda for each reminder, but at most 4 at the same time, which seems to be what you're asking for. Though I'm not sure it's what you need, since you don't seem to release (dispose) the channel after usage, to be used for the next reminder.

svick
  • 236,525
  • 50
  • 385
  • 514
0

It is not clear where the problem is, but it seems that it is not that the cinstructor is called after the method. Iwould advise you to change the constructor code to:

public OutboundDial(TelephonyServer telephonyServer)
{
    m_TelephonyServer = telephonyServer;
    Console.WriteLine(m_Telephonyserver);
}

And then you will see for sure that the constructor is complete.

Also, add some Console.WriteLine with useful informatino after each line in RunScript - then you will see where the error is coming from.

ShayD
  • 920
  • 8
  • 18
  • You are right, the constructor is called before the method. I reedited the problem. It seems the code ignored the `MaxDegreeOfParallelism` as 4. It had 10 items in the queue, but it run the constructor and the method 10 times instead of 4. –  Oct 13 '14 at 14:21