1

I have a telephony application, in which I want to invoke simultaneous calls,. Each call will occupy a channel or port. So I added all channels to a BlockingCollection. The application is a windows service.

Let's see the code.

    public static BlockingCollection<Tuple<ChannelResource, string>> bc = new BlockingCollection<Tuple<ChannelResource, string>>();
    public static List<string> list = new List<string>();// then add 100 test items to it. 

The main application has the code:

            while (true)
            {
                ThreadEvent.WaitOne(waitingTime, false);

                lock (SyncVar)
                {
                    Console.WriteLine("Block begin");
                    for (int i = 0; i < ports; i++)
                    {
                        var firstItem = list.FirstOrDefault();
                        if (bc.Count >= ports)
                            bc.CompleteAdding();
                        else
                        {
                            ChannelResource cr = OvrTelephonyServer.GetChannel();        
                            bc.TryAdd(Tuple.Create(cr, firstItem));
                            list.Remove(firstItem);
                        }
                    }

                    pc.SimultaneousCall();
                    Console.WriteLine("Blocking end");
                    if (ThreadState != State.Running) break;
                }

Now for the simultaneous call code:

 public void SimultaneousCall()
    {
        Console.WriteLine("There are {0} channels to be processed.", bc.Count);
        var workItemBlock = new ActionBlock<Tuple<ChannelResource, string>>(
           workItem =>
           {
               ProcessEachChannel(workItem);
           });

        foreach (var workItem in bc.GetConsumingEnumerable())
        {
            bool result = workItemBlock.SendAsync(workItem).Result;
        }

        workItemBlock.Complete();
    }

    private void ProcessEachChannel(Tuple<ChannelResource, string> workItem)
    {
        ChannelResource cr = workItem.Item1;
        string sipuri = workItem.Item2;
        VoiceResource vr = workItem.Item1.VoiceResource; 
        workItem.Item1.Disconnected += new Disconnected(workItemItem1_Disconnected);
        bool success = false;
        try
        {
            Console.WriteLine("Working on {0}", sipuri);
            DialResult dr = new DialResult();
             // blah blah for calling....
        }
        catch (Exception ex)
        {
             Console.WriteLine("Exception: {0}", ex.Message);
        }
        finally
        {
            if (cr != null && cr.VoiceResource != null)
            {
                cr.Disconnect();
                cr.Dispose();
                cr = null;
                Console.WriteLine("Release channel for item {0}.", sipuri);
            }
        }
    }

The question was when I tested the application with 4 ports, I thought the code should reach at

Console.WriteLine("Blocking end");

However it was not. Please see the snapshot. image

The application is just hanging on after releasing the last channel. I guess that I may use the blockingcollection incorrectly. Thanks for help.

UPDATE:

Even I changed the code by using POST action as below, the situation is still unchanged.

private bool ProcessEachChannel(Tuple<ChannelResource, string> workItem)
    {
        // blah blah to return true or false respectively.
public void SimultaneousCall()
    {
        Console.WriteLine("There are {0} channels to be processed.", bc.Count);
        var workItemBlock = new ActionBlock<Tuple<ChannelResource, string>>(
           workItem =>
           {
               bool success = ProcessEachChannel(workItem);
           });

        foreach (var workItem in bc.GetConsumingEnumerable())
        {
            workItemBlock.Post(workItem);
        }

        workItemBlock.Complete();
    }
svick
  • 236,525
  • 50
  • 385
  • 514
  • @ScottChamberlain, I want to return the call result to summary it. Say how many fail or success, so I used `ActionBlock.SendAsync(...).Result`. –  Aug 06 '14 at 20:23
  • I was wrong you are using bc incorrectly, not at a computer where I can type up a answer – Scott Chamberlain Aug 06 '14 at 20:26
  • @ScottChamberlain, no hurry. I can wait. –  Aug 06 '14 at 20:27
  • If `ProcessEachChannel` is synchronous, why not just use `Parallel.ForEach` instead of bothering with all of TPL? It'd still be good to figure out your problem, but that just seems simpler for this implementation. – Matthew Haugen Aug 07 '14 at 18:26
  • @MatthewHaugen, I had the old thread at http://stackoverflow.com/questions/22688679/process-queue-with-multithreading-or-tasks. One comment indicated that "This won't work if consumer keeps working and adding stuff into the queue" by oleksii. –  Aug 07 '14 at 18:33

1 Answers1

2

I believe the problem is that you never call bc.CompleteAdding(): the if means it would be called in ports + 1-th iteration of the loop, but the loop iterates only ports-times. Because of this, GetConsumingEnumerable() returns a sequence that never ends, which means the foreach inside SimultaneousCall() blocks forever.

I think the right solution is to call bc.CompleteAdding() after the for loop, not in an impossible condition inside it.

svick
  • 236,525
  • 50
  • 385
  • 514
  • @Love, sorry I never got to answering yesterday but this answer is pretty much covers what I was going to say. – Scott Chamberlain Aug 07 '14 at 18:27
  • Can I discard `ActionBlock` code? Just use a simple one, `foreach (var workItem in bc.GetConsumingEnumerable()) { bool success = ProcessEachChannel(workItem); } `Also I want to empty the blockingcollection. –  Aug 07 '14 at 18:58
  • @Love Yeah, you can certainly do that, though it means your code will behave differently: with `ActionBlock`, you're not waiting for the processing to complete. – svick Aug 07 '14 at 19:01