5

Problem: I've got tons of emails to send, presently, an average of 10 emails in the queue at any point in time. The code I have process the queue one at a time; that is, receive the message, process it and eventually send the email. This cause a considerably delay in sending emails to users when they signup for the service.

I've begun to think of modifying the code to process the messages in parrallel say 5 asynchronously. I'm imagining writing a method and using the CTP to call this method in parallel, say, 5 times.

I'm a little bit lost in how to implement this. The cost of making a mistake is exceedingly great as users will get disappointed if things go wrong.

Request: I need help in writing code that process messages in Azure service bus in parallel. Thanks.

My code in a nutshell.

Public .. Run()
{
   _myQueueClient.BeginReceive(ProcessUrgentEmails, _myQueueClient);
}

void ProcessUrgentEmails(IAsyncResult result)
{
   //casted the `result` as a QueueClient
   //Used EndReceive on an object of BrokeredMessage
   //I processed the message, then called
   sendEmail.BeginComplete(ProcessEndComplete, sendEmail);
 }


 //This method is never called despite having it as callback function above.
 void ProcessEndComplete(IAsyncResult result)
 {
     Trace.WriteLine("ENTERED ProcessEndComplete method...");
     var bm = result.AsyncState as BrokeredMessage;
     bm.EndComplete(result); 
 }
jjnguy
  • 136,852
  • 53
  • 295
  • 323
olatunjee
  • 351
  • 2
  • 3
  • 12

1 Answers1

7

This page gives you performance tips when using Windows Azure Service Bus.

About parallel processing, you could have a pool of threads for processing, and every time you get a message, you just grab one of that pool and assign it a message. You need to manage that pool.

OR, you could retrieve multiple messages at once and process them using TPL... for example, the method BeginReceiveBatch/EndReceiveBatch allows you to retrieve multiple "items" from Queue (Async) and then use "AsParallel" to convert the IEnumerable returned by the previous methods and process the messages in multiple threads.

VERY simple and BARE BONES sample:

var messages = await Task.Factory.FromAsync<IEnumerable<BrokeredMessage>>(Client.BeginReceiveBatch(3, null, null), Client.EndReceiveBatch);

messages.AsParallel().WithDegreeOfParallelism(3).ForAll(item =>
{
    ProcessMessage(item);
});

That code retrieves 3 messages from queue and processes then in "3 threads" (Note: it is not guaranteed that it will use 3 threads, .NET will analyze the system resources and it will use up to 3 threads if necessary)

You could also remove the "WithDegreeOfParallelism" part and .NET will use whatever threads it needs.

At the end of the day there are multiple ways to do it, you have to decide which one works better for you.

UPDATE: Sample without using ASYNC/AWAIT

This is a basic (without error checking) sample using regular Begin/End Async pattern.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.ServiceBus;
using Microsoft.ServiceBus.Messaging;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace WorkerRoleWithSBQueue1
{
    public class WorkerRole : RoleEntryPoint
    {
        // The name of your queue
        const string QueueName = "QUEUE_NAME";
        const int MaxThreads = 3;

        // QueueClient is thread-safe. Recommended that you cache 
        // rather than recreating it on every request
        QueueClient Client;
        bool IsStopped;
        int dequeueRequests = 0;

        public override void Run()
        {
            while (!IsStopped)
            {
                // Increment Request Counter
                Interlocked.Increment(ref dequeueRequests);

                Trace.WriteLine(dequeueRequests + " request(s) in progress");

                Client.BeginReceive(new TimeSpan(0, 0, 10), ProcessUrgentEmails, Client);

                // If we have made too many requests, wait for them to finish before requesting again.
                while (dequeueRequests >= MaxThreads && !IsStopped)
                {
                    System.Diagnostics.Trace.WriteLine(dequeueRequests + " requests in progress, waiting before requesting more work");
                    Thread.Sleep(2000);
                }

            }
        }


        void ProcessUrgentEmails(IAsyncResult result)
        {
            var qc = result.AsyncState as QueueClient;
            var sendEmail = qc.EndReceive(result);
            // We have received a message or has timeout... either way we decrease our counter
            Interlocked.Decrement(ref dequeueRequests);

            // If we have a message, process it
            if (sendEmail != null)
            {
                var r = new Random();
                // Process the message
                Trace.WriteLine("Processing message: " + sendEmail.MessageId);
                System.Threading.Thread.Sleep(r.Next(10000));

                // Mark it as completed
                sendEmail.BeginComplete(ProcessEndComplete, sendEmail);
            }

        }


        void ProcessEndComplete(IAsyncResult result)
        {
            var bm = result.AsyncState as BrokeredMessage;
            bm.EndComplete(result);
            Trace.WriteLine("Completed message: " + bm.MessageId);
        }


        public override bool OnStart()
        {
            // Set the maximum number of concurrent connections 
            ServicePointManager.DefaultConnectionLimit = 12;

            // Create the queue if it does not exist already
            string connectionString = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ConnectionString");
            var namespaceManager = NamespaceManager.CreateFromConnectionString(connectionString);
            if (!namespaceManager.QueueExists(QueueName))
            {
                namespaceManager.CreateQueue(QueueName);
            }

            // Initialize the connection to Service Bus Queue
            Client = QueueClient.CreateFromConnectionString(connectionString, QueueName);
            IsStopped = false;
            return base.OnStart();
        }

        public override void OnStop()
        {
            // Waiting for all requestes to finish (or timeout) before closing
            while (dequeueRequests > 0)
            {
                System.Diagnostics.Trace.WriteLine(dequeueRequests + " request(s), waiting before stopping");
                Thread.Sleep(2000);
            }
            // Close the connection to Service Bus Queue
            IsStopped = true;
            Client.Close();
            base.OnStop();
        }
    }
}

Hope it helps.

Daniel
  • 1,424
  • 1
  • 12
  • 19
  • Thank you Daniel for the response. I implemented (with slight alteration) the example at [MSDN](http://msdn.microsoft.com/en-us/library/windowsazure/hh528527.aspx). It works fine but the [ProcessEndComplete](http://msdn.microsoft.com/en-us/library/windowsazure/hh528527.aspx#code-snippet-2) method isnt called at all so the same message is processed over and over again. – olatunjee Jun 05 '13 at 09:00
  • The method with signature `BrokeredMessage.BeginComplete(AsyncCallBack, BrokeredMessage);` is what isnt called at all. – olatunjee Jun 05 '13 at 09:06
  • `ProcessEndComplete` is called when the app retrieves a message from the queue, and in this method YOU call `BeginComplete`. I am guessing your code is reaching `ProcessEndComplete`, right?... Do you get any errors when YOU call `BeginComplete`? – Daniel Jun 05 '13 at 13:34
  • Nay. I got no error. I tried stepping into the code from the debugger but it doesnt enter the `ProcessEndComplete` method at all let alone called the `BeginComplete` which resides in the `ProcessEndComplete` method. – olatunjee Jun 05 '13 at 18:45
  • My AsyncCallback function is successfully called on the invocation of the `BeginReceive` method. After successfully processing the message, I called `BrokeredMessage.BeginComplete(AsyncCallBack, BrokeredMessage)` method. It just passed this method without any sign of executing the callback function (which happens to be `ProcessEndComplete`) without stepping into it. – olatunjee Jun 05 '13 at 18:49
  • I just show portion of the code in my edit above. Please refer to my edited question. – olatunjee Jun 05 '13 at 19:34
  • Your program is ending before the method `ProcessEndCompleted` gets called. In your sample, you call `BeginReceive` in a the main RUN method, that one starts an **Async** call (**which spawns a new thread**), right after that your program ends and nothing else happens. You need to wait until the thread completes before exiting your app. – Daniel Jun 05 '13 at 20:38
  • You mean I shouldn't have called `BeginReceive` from the RUN method? Could you please put me through on what to correct from the code? – olatunjee Jun 05 '13 at 20:49
  • The RUN method is not exited until a certain condition becomes false. All the method in the RUN method is wrapped in a `while` loop so they are trapped inside and runs continuously pending when the while-loop condition remain false. The RUN method is that of a WorkerRole in Azure. – olatunjee Jun 05 '13 at 20:55
  • I added an example that do not use ASYNC/AWAIT to control threads (but you should use it if you can, the code would be much cleaner and easier to read) – Daniel Jun 06 '13 at 02:01
  • @Daniel - Whether parallel message processing in Azure service bus Implementation is possible in java ? If yes, can you pls suggest some information for the same. – Debugger Jan 05 '21 at 17:15
  • @Debugger, I do no know Java, I am guessing it supports threads, so you should be able to do something similar. – Daniel Feb 17 '21 at 14:01