Totty
Totty

Reputation: 277

Async jQuery database loop - Help on implementing progress bar

So I am trying to add an async progress bar on a really slow and long query that inserts a bunch of rows to a database. My implementation is based off this example: http://blog.janjonas.net/2012-01-02/asp_net-mvc_3-async-jquery-progress-indicator-long-running-tasks

Here is the javascript code for the progress bar

function updateMonitor(taskId, status) {
        $("#monitors").html("Task [" + taskId + "]: " + status);
    }
//other code
if (doSend == true) {
            $.post("/SendBatch/HandleBatchRequest", {
                //other code
            },
            function (taskId) {

                // Init monitors
                //breakpoint here only stops it once the "/SendBatch/HandleBatchRequest" is done. PROBLEM.
                $("#monitors").append($("<p id='" + taskId + "'/>"));
                updateMonitor(taskId, "Started");

                // Periodically update monitors
                var intervalId = setInterval(function () {
                    $.post("SendBatch/Progress", { id: taskId }, function (progress) {
                    if (progress >= 100) {
                        updateMonitor(taskId, "Completed");
                        clearInterval(intervalId);
                    } else {
                        updateMonitor(taskId, progress + "%");
                    }
                });
            }, 100);
        }       
        ,"html");

Then there is the DIV within the display part of the website

<div id="monitors"></div>

Here is how the controller looks

public SendBatchController
    //some code
private static IDictionary<Guid, int> tasks = new Dictionary<Guid, int>();

public ActionResult HandleBatchRequest(
        //some code
    )
    {
        var taskId = Guid.NewGuid();
        tasks.Add(taskId, 0);



        var batchId = Guid.NewGuid().ToString("N");
        var costd = cost.ToDecimal();

        IEnumerable<BatchListModel> customers;

        try
        {
            customers = new CustomerService(_customerRepository.Session).GetCustomers(
                //some code
                );
        }
        catch (Exception err)
        {
            return Json(err.Message);
        }
        if (doSend)
        {
            var sent = 0;

            foreach (var c in customers)
            {
                try
                {
                    var usr = _customerRepository.LoadByID(c.ID);

                    var message = new ComLog
                                      {
                                          //insertions to log
                                      };

                    _comLogRepository.Save(message);
                    sent++;

                    //progress bar part inside here that is important comes here:
                    tasks[taskId] = sent;

                }
                catch (Exception e)
                {
                    Log.WriteLine("ERR:" + e);
                }

                tasks.Remove(taskId);

            }
            return Json(taskId);

        }

        return Json(customers.Count() + " customers");
    }

    public ActionResult Progress(Guid id)
    {
        return Json(tasks.Keys.Contains(id) ? tasks[id] : 100);
    }

This does not work. The process works in the background. The DIV does show up after the query is done with a "Complete" message. It does not show during the query. I am not sure how async stuff works this works so there might be some logic error here. For instance, does it even know how many customers there is in total? Another key part of the code is the "tasks[taskId] = sent;" wich I am not sure is done correctly.

Upvotes: 0

Views: 1000

Answers (3)

Ctrl_Alt_Defeat
Ctrl_Alt_Defeat

Reputation: 4009

Why not just add a loading gif to your website (you can download them here - http://www.ajaxload.info/) Then posistion this loading gif in a div on your page - whereever your are wanting it to display.

Something like the below

<div><img class="hidden " id="LoadingGif" src='../Content/Styles/images/loading.gif' height="100" /></div>

Then in your js file simplay remove class hidden on the LoadingGif id and possibly add class hidden to your monitors div.

hidden class should be like below in your site.css

.hidden  {visibility:hidden; display:none;}

Upvotes: 0

Steven V
Steven V

Reputation: 16595

On the javascript side, the function that begins the progress check won't fire until after the successful completion of HandleBatchRequest request. The third parameter of $.post() is what is fired after a successful request.

You'll either need to have your C# controller return the task ID right away, and ferry off the database insert into another thread/background worker (more on this below). Or have Javascript randomly generate the taskId and pass into the C# controller.

From there, as Brad mentioned, you don't want to do setInterval with a ajax request inside of it. Since AJAX is asyncronous, you could be waiting a while before the server returns a response. To prevent multiple requests from happening at once and causing undo stress on the backend server, you'll want to do something like:

var taskId = 0;
$.post('/SendBatch/HandleBatchRequest', {}, function(r) {
    taskId = r;  // set task id from 
    $("#monitors").append($("<p id='" + taskId + "'/>"));
    CheckProgress();
}, 'html');

function CheckProgress() {
    $.post("SendBatch/Progress", { id: taskId }, function (progress) {
    if (progress >= 100) {
        updateMonitor(taskId, "Completed");
    } else {
        updateMonitor(taskId, progress + "%");
        setTimeout(function() {
            CheckProgress()
        }, 1000);
    }
}

On the C# side of things, The controller is instantiated on every web request, so when you go check the progress, tasks will be empty. You'll need to off load the actual records into some sort of background worker/thread that does the actual leg work of inserting the records from the controller. If you're doing this pretty often, you may want to look into some sort of background service installed on the server itself that continuously runs to optimize things and give you the best flexibility going forward.

Upvotes: 1

Brad M
Brad M

Reputation: 7898

I would highly recommend against using ajax inside of a setInterval function. Instead, use a setTimeout in the success/complete callback function of the $.post(). This will guarantee that the requests are generated sequentially.

If you fire off ajax requests every 100ms, they will quickly fall out of sync and undoubtedly cause issues.

Upvotes: 0

Related Questions