locked
EndInvoke When Using Multiple Threads RRS feed

  • Question

  • User-186058747 posted

    I have a web service that accepts some data, opens a loop, and then sends the request to about a dozen of external web services to collects responses.  The problem is that all of the web service requests take different amounts of time to response - some instantly, and some take up to 20, 30 seconds.

    I have been doing it like this:

    Using branchReader As SqlDataReader = command.ExecuteReader()
                                    If branchReader.HasRows Then
                                        While branchReader.Read
                                            'data gets assigned to the partnerBranch class here
    
                                                ' create the delegate
                                                Dim delAssemblePings As New PingDelegate(AddressOf Partners.Broadcast)
    
                                                ' call the BeginInvoke function!
                                                Dim tag As IAsyncResult = delAssemblePings.BeginInvoke(partnerBranch, Nothing, Nothing)
    
                                            End If
                                        End While
                                    End If
                                    branchReader.Close()
                                End Using


    Now, what I want to happen is when ALL of the web service requests from Partners.Broadcast are done, I want to proceed with the final step.  But right now, just to be sure they are all done, I am using:

    'after data reader loops and sends all of the async web requests
    
    thread.sleep(15000) 'sleep for 15 seconds to make sure all requests are done
    
    if partnerBranch.Sold = true then
       'will call function that sells to the partner with the highest offer in broadcast
    end if

    What I want to do though is NOT have to wait 15 seconds each time. I want to be able to call the sold as soon as ALL of the web requests are done, but NOT UNTIL all of them are done.  Sometimes it is 5 seconds, sometimes 20.  I omitted the EndInvoke though, because it will block until the async request is complete, and that is not what I want.

     

    Monday, July 22, 2013 12:44 PM

Answers

  • User-760709272 posted

    With a param.  This is an int, but like I said it could be an object with properties if you need to pass multiple things

        public partial class WebForm1 : System.Web.UI.Page
        {
            public Random r;
            private int count;
            AutoResetEvent reset;
    
            protected void Page_Load(object sender, EventArgs e)
            {
                r = new Random();
    
                // Number of tasks we're going to kick off
                count = 5;
    
                // Create a new AutoResetEvent and set it to "unsignalled"
                // A WaitHandle will stop thread execution until the handle becomes "signalled" so we
                // want its initial state to be unsignaled
                reset = new AutoResetEvent(false);
    
                // Add our 5 tasks to the threadpool
                int t = r.Next(5000);
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), t);
                t = r.Next(5000);
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), t);
                t = r.Next(5000);
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), t);
                t = r.Next(5000);
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), t);
                t = r.Next(5000);
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), t);
    
                // tell our AutoResetEvent to wait until it is signalled, or until 1 minute has elasped.
                // If we just call WaitOne it will wait forever until signalled, but this gives us a get out in case
                // something unexpected happens, or one of the processess just takes too long
                reset.WaitOne(new TimeSpan(0, 1, 0));
    
                // The code will only get here after the AutoResetEvent has been triggered
                if (count == 0)
                    System.Diagnostics.Debug.WriteLine("Done"); // all tasks were done
                else
                    System.Diagnostics.Debug.WriteLine("Timed out"); // some were not so we probably got here from the 1 minute timeout
            }
    
            public void DoWork(object stateInfo)
            {
                // This is where you will call your webservices.  I'll just sleep the thread instead.
    
                int t = (int)stateInfo;
    
                System.Diagnostics.Debug.WriteLine("Sleep for {0}ms", t);
                // Sleep
                System.Threading.Thread.Sleep(t);
    
                // State we are finished
                Finished();
    
            }
    
            public void Finished()
            {
                // We don't want this code being called multiple times so we'll lock it to a single thread
                lock (this)
                {
                    // Decrease the task count
                    count -= 1;
    
                    System.Diagnostics.Debug.WriteLine("Finished, {0} left", count);
    
                    // If there are no more tasks, signal the AutoResetEvent
                    if (count == 0)
                        reset.Set();
                }
            }
        }

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Monday, July 22, 2013 6:37 PM

All replies

  • User-760709272 posted

    Someone might have a better idea, but you could fire them all off on different threads (creating them, or using ThreadPool).  You could have a variable that has the number of running requests and a WaitHandle.  When each request finishes it decreases the variable that holds the number of running processes, and the last one to finish trips the wait handle causing the main thread to re-start.

    Or could you look into parallel processing?  Not sure if that would be possible to use here, but I believe it is used in a for loop (or similar) and it maintains when all the tasks have finished and exits the loop when they have.

    Monday, July 22, 2013 4:31 PM
  • User-186058747 posted

    I was looking into the parallel processing, but in a way, isn't that what I am doing now?

    I have all of my web services set to timeout after 15 seconds, I loop through all of them and call them via a new delegate, then I put the main thread to sleep long enough for all of the services to timeout, or complete.  Seems barbaric, but isn't that the same result really?

    I just didn't know if the lack of EndInvoke for every Invoke would cause an issue - but it hasn't seemed to... 

    Monday, July 22, 2013 4:46 PM
  • User-760709272 posted

    It depends how it knows the execution is done.  I doubt it uses constant loops or thread sleeps though.  If you're not sure about parallel processing, kicking the threads off then waiting for a wait handle to be signaled is probably the next best option and at least you know that's not waiting around needlessly, and not consuming cpu cycles either.  The problem with sleep/wake/check/sleep/wait/check is that if your sleeps are too long then the process is taking longer than needs be, and if too short you are using cpu cycles by looping.  By kicking each request off on its own thread then waiting at a wait handle, no resources are being used.  When each request comes back from its asynch method you knock it off as another completed task and the last task signals the wait.  No uncessessary waiting, looping or cpu.

    Monday, July 22, 2013 5:26 PM
  • User-186058747 posted

    AidyF

    By kicking each request off on its own thread then waiting at a wait handle, no resources are being used.  When each request comes back from its asynch method you knock it off as another completed task and the last task signals the wait.  No uncessessary waiting, looping or cpu.



    This all looks simple enough, (judging from this link: http://msdn.microsoft.com/en-us/library/system.threading.waithandle.aspx), but I'm a little confused on how I add parameters when I add to the thread queue.  Also the waitHandle array - I assume I need to scale the array to the dimension of the loop I will be running through while adding to the queue? 

    Monday, July 22, 2013 5:59 PM
  • User-760709272 posted

    I've put a basic example below.  You can only add a single parameter when adding to the queue.  This could be a complex object with properties though.

        public partial class WebForm1 : System.Web.UI.Page
        {
            public Random r;
            private int count;
            AutoResetEvent reset;
    
            protected void Page_Load(object sender, EventArgs e)
            {
                r = new Random();
    
                // Number of tasks we're going to kick off
                count = 5;
    
                // Create a new AutoResetEvent and set it to "unsignalled"
                // A WaitHandle will stop thread execution until the handle becomes "signalled" so we
                // want its initial state to be unsignaled
                reset = new AutoResetEvent(false);
    
                // Add our 5 tasks to the threadpool
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
    
                // tell our AutoResetEvent to wait until it is signalled, or until 1 minute has elasped.
                // If we just call WaitOne it will wait forever until signalled, but this gives us a get out in case
                // something unexpected happens, or one of the processess just takes too long
                reset.WaitOne(new TimeSpan(0, 1, 0));
    
                // The code will only get here after the AutoResetEvent has been triggered
                if (count == 0)
                    System.Diagnostics.Debug.WriteLine("Done"); // all tasks were done
                else
                    System.Diagnostics.Debug.WriteLine("Timed out"); // some were not so we probably got here from the 1 minute timeout
            }
    
            public void DoWork(object stateInfo)
            {
                // This is where you will call your webservices.  I'll just sleep the thread instead.
    
                // Choose a random length of time to wait
                int t = r.Next(5000);
    
                System.Diagnostics.Debug.WriteLine("Sleep for {0}ms", t);
                // Sleep
                System.Threading.Thread.Sleep(t);
    
                // State we are finished
                Finished();
    
            }
    
            public void Finished()
            {
                // We don't want this code being called multiple times so we'll lock it to a single thread
                lock (this)
                {
                    // Decrease the task count
                    count -= 1;
    
                    System.Diagnostics.Debug.WriteLine("Finished, {0} left", count);
    
                    // If there are no more tasks, signal the AutoResetEvent
                    if (count == 0)
                        reset.Set();
                }
            }
        }
    
     

    Monday, July 22, 2013 6:33 PM
  • User-760709272 posted

    With a param.  This is an int, but like I said it could be an object with properties if you need to pass multiple things

        public partial class WebForm1 : System.Web.UI.Page
        {
            public Random r;
            private int count;
            AutoResetEvent reset;
    
            protected void Page_Load(object sender, EventArgs e)
            {
                r = new Random();
    
                // Number of tasks we're going to kick off
                count = 5;
    
                // Create a new AutoResetEvent and set it to "unsignalled"
                // A WaitHandle will stop thread execution until the handle becomes "signalled" so we
                // want its initial state to be unsignaled
                reset = new AutoResetEvent(false);
    
                // Add our 5 tasks to the threadpool
                int t = r.Next(5000);
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), t);
                t = r.Next(5000);
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), t);
                t = r.Next(5000);
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), t);
                t = r.Next(5000);
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), t);
                t = r.Next(5000);
                ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), t);
    
                // tell our AutoResetEvent to wait until it is signalled, or until 1 minute has elasped.
                // If we just call WaitOne it will wait forever until signalled, but this gives us a get out in case
                // something unexpected happens, or one of the processess just takes too long
                reset.WaitOne(new TimeSpan(0, 1, 0));
    
                // The code will only get here after the AutoResetEvent has been triggered
                if (count == 0)
                    System.Diagnostics.Debug.WriteLine("Done"); // all tasks were done
                else
                    System.Diagnostics.Debug.WriteLine("Timed out"); // some were not so we probably got here from the 1 minute timeout
            }
    
            public void DoWork(object stateInfo)
            {
                // This is where you will call your webservices.  I'll just sleep the thread instead.
    
                int t = (int)stateInfo;
    
                System.Diagnostics.Debug.WriteLine("Sleep for {0}ms", t);
                // Sleep
                System.Threading.Thread.Sleep(t);
    
                // State we are finished
                Finished();
    
            }
    
            public void Finished()
            {
                // We don't want this code being called multiple times so we'll lock it to a single thread
                lock (this)
                {
                    // Decrease the task count
                    count -= 1;
    
                    System.Diagnostics.Debug.WriteLine("Finished, {0} left", count);
    
                    // If there are no more tasks, signal the AutoResetEvent
                    if (count == 0)
                        reset.Set();
                }
            }
        }

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Monday, July 22, 2013 6:37 PM
  • User-186058747 posted

    What if I need to send it two complex objects with properties ByRef?

    Monday, July 22, 2013 6:50 PM
  • User-760709272 posted

    Have an object that has a property for each of your other objects and send that :)  Chances are you're going to have to refactor things slightly, maybe create an object simply for use in holding the data the worker method needs, you will poke the data into this object from the right sources, then pass the object to the method as the objectState.

    Monday, July 22, 2013 6:55 PM
  • User-186058747 posted

    Got it!

    Monday, July 22, 2013 7:00 PM
  • User-186058747 posted

    Actually...

    I want it to wait for ALL of them to execute, and then move on.

    I only want them performed once each, so if I perform SyncLock at the end of function - will that lock just that thread while the others finish?

    So that said, I'm a little confused how to perform the WaitHandle.WaitAll(?) with your example.

    Monday, July 22, 2013 7:37 PM
  • User-186058747 posted

    Actually, I think I could do something like this:

    Private Shared waitHandles() As WaitHandle
    
    '.....
    if reader.hasrows then
    
    dim x as integer = 0
    
    while reader.read

    waitHandles.SetValue(New AutoResetEvent(False), x) Dim tPacket As New tPacket 'contains my 2 classes with properties AND new AutoResetEvent tPacket.MyClass1 = MyClass1 tPacket.MyClass2 = MyClass2
    tPingPacket.AutoResetEvent = waitHandles(x) waitHandles.SetValue(tPacket.AutoResetEvent, x) ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf asyncServices.Broadcast), tPacket) x += 1 end while end if 'loop is done WaitHandle.WaitAll(waitHandles)

    'THIS THROWS:
    The waitHandles parameter cannot be null.
    'Now, lookup results and complete if available


     

    Monday, July 22, 2013 8:08 PM
  • User-760709272 posted

    Actually...

    I want it to wait for ALL of them to execute, and then move on.

    I only want them performed once each, so if I perform SyncLock at the end of function - will that lock just that thread while the others finish?

    So that said, I'm a little confused how to perform the WaitHandle.WaitAll(?) with your example.

    That is what it does.  The Finished method uses locking but only to make sure it is single threaded, each time it is called, the code runs right through.  The most that lock will make a thread wait is the time it takes the active thread to run the code, and in our case you're talking 1 or 2 ticks.  Anyway, here is the code with WaitAll.

        public partial class WebForm1 : System.Web.UI.Page
        {
            public Random r = null;
            AutoResetEvent[] reset = new AutoResetEvent[5];
    
            protected void Page_Load(object sender, EventArgs e)
            {
                r = new Random();
    
                for (int i = 0; i < 5; i++)
                {
                    reset[i] = new AutoResetEvent(false);
    
                    ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), reset[i]);
                }
    
                WaitHandle.WaitAll(reset);
    
                System.Diagnostics.Debug.WriteLine("Done");
    
            }
    
            public void DoWork(object stateInfo)
            {
                // This is where you will call your webservices.  I'll just sleep the thread instead.
                
                int t = r.Next(5000);
    
                System.Diagnostics.Debug.WriteLine("Sleep for {0}ms", t);
                // Sleep
                System.Threading.Thread.Sleep(t);
    
                AutoResetEvent reset = (AutoResetEvent)stateInfo;
                reset.Set();
    
            }
    

    Monday, July 22, 2013 8:43 PM