0

I have a action method in ASP.NET MVC project (prod app) which imports csv file records and displays progress on progress bar, for this implementation i used this https://stackoverflow.com/a/2928148/1386991, this works well with brand new MVC project (poc app).

But, when I put exact same controller and view (with js code) in my original project (prod app), CheckTaskProgress methods hits only when RunLongTask completes the task hence users sees no progress bar update and sudden 100%, that's totally opposite behavior from what I had in brand new project (poc app). Here's visible behavior screenshot for your reference https://www.youtube.com/watch?v=wNa5De0lmdA.

I checked MVC package versions and web.config, this looks fine. Any clue to check ?

Here's controller code:

using System;
using System.Web.Mvc;
using System.Threading.Tasks;

namespace Prod.Web.Controllers
{
    public class DemoController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public async Task<ActionResult> RunLongTask()
        {
            string id = "task_id1";  //should be unique
            int maxcount = 200;

            AsyncManager.OutstandingOperations.Increment();
            await Task.Factory.StartNew(taskId =>
            {
                HttpContext.Application["t_max_" + taskId] = maxcount;
                for (int i = 0; i < maxcount; i++)
                {
                    System.Threading.Thread.Sleep(100);
                    HttpContext.Application["t_prog_" + taskId] = i;
                }
                AsyncManager.OutstandingOperations.Decrement();
            }, id);

            return Json(new { status = true, ProgressCurrent = maxcount, ProgressMax = maxcount, ProgressPercent = 100 });
        }

        public ActionResult CheckTaskProgress()
        {
            string id = "task_id1";  //should be unique

            var progressCurrent = HttpContext.Application["t_prog_" + id];
            var progressMax = HttpContext.Application["t_max_" + id];
            decimal progressPercent = (Convert.ToDecimal(progressCurrent) / Convert.ToDecimal(progressMax)) * 100M;

            return Json(new
            {
                ProgressCurrent = progressCurrent,
                ProgressMax = progressMax,
                ProgressPercent = Convert.ToInt32(progressPercent)
            }, JsonRequestBehavior.AllowGet);
        }
    }
}

And here's view with js code:

@{
    ViewBag.Title = "Run ajax long running process and display on progress bar";
}
<div class="main-wrap">
    <div class="container">
        <div class="row py-5">
            <div class="col-12">
                <button class="btn btn-primary runlongtask">
                    <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" style="display:none;"></span>
                    Run ajax long running process and display on progress bar
                </button>
            </div>
            <div class="col-12 pt-5">
                <div class="progress">
                    <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="0" style="width:0%"></div>
                </div>
            </div>
        </div>
    </div>
</div>
@section foot{
    <script>
        $(function () {

            $('.runlongtask').click(function () {
                var triggerbtn = $(this);
                triggerbtn.prop('disabled', true).children('.spinner-border').show();
                $.ajax({
                    type: 'POST',
                    url: '/demo/runlongtask',
                    success: function (data) {
                        if (data.status) {
                            longTaskUpdateProgress(data.ProgressCurrent, data.ProgressMax, data.ProgressPercent);
                            triggerbtn.prop('disabled', false).children('.spinner-border').hide();
                            window.clearInterval(longTaskInterval);
                        }
                    }
                });

                var longTaskInterval = window.setInterval(function () {
                    console.log('interval func invoked');
                    $.ajax({
                        type: 'GET',
                        url: '/demo/checktaskprogress',
                        success: function (data) {
                            longTaskUpdateProgress(data.ProgressCurrent, data.ProgressMax, data.ProgressPercent);
                        }
                    });
                }, 5000);

                var longTaskUpdateProgress = function (ProgressCurrent, ProgressMax, ProgressPercent) {
                    console.log('progressbar func invoked');
                    $('.progress-bar')
                        .attr('aria-valuenow', ProgressPercent)
                        .css('width', ProgressPercent + '%')
                        .html(ProgressPercent + '% Completed');

                    console.log(ProgressPercent + ' Completed');
                    console.log(ProgressCurrent + ' executed out of ' + ProgressMax);
                };
            });

        })
    </script>
}

Here's the source code of my project (poc app) on GitHub https://github.com/itorian/Run-Ajax-Long-Process-With-Progress-Bar.

Abhimanyu
  • 2,173
  • 2
  • 28
  • 44

2 Answers2

3

There are a couple of possibilities:

  1. Your existing project uses session state. If this is the case, you'll need to opt-out of session state for these methods; otherwise, the CheckTaskProgress call will block until RunLongTask completes, since it is waiting for the session to be available.
  2. Your existing project has not been updated for async/await. If this is the case, you should add an appropriate httpRuntime targetFramework value.

Side notes:

  • The answer you're using is from two decades ago. There are better solutions available today. SignalR, for example.
  • This solution isn't going to work well in a web farm, since task statuses are all kept in-memory. A better solution is to store the task status in some shared place.
  • You should avoid StartNew. I'm not even sure why it's in the original code; it should work the same without the StartNew and AsyncManager calls.
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
1

Its not recommend to mix async and non async (blocking) code, so the following statement will cause you issues:

System.Threading.Thread.Sleep(100);

Replace it with:

await Task.Delay(100);

The rule of thumb is Async all the way down

Reference:
When to use Task.Delay, when to use Thread.Sleep?

Ralph Willgoss
  • 11,750
  • 4
  • 64
  • 67
  • Just used this in poc and prod projects, task immediately completes with 100% on progress in both projects :( you can try this with this poc project https://github.com/itorian/Run-Ajax-Long-Process-With-Progress-Bar – Abhimanyu Jun 18 '19 at 11:34
  • i need help to figure out why poc codes (https://github.com/itorian/Run-Ajax-Long-Process-With-Progress-Bar) not work as expected in prod project – Abhimanyu Jun 18 '19 at 11:37
  • 1
    This shouldn't make a difference at all. – Tvde1 Jun 18 '19 at 11:53
  • @Tvde1 above changes creates bug on poc project – Abhimanyu Jun 18 '19 at 12:00
  • No, its uncovered another issue. You shouldn't mix blocking and non blocking code. – Ralph Willgoss Jun 18 '19 at 12:02
  • 1
    true, looks like i have issue with js code now, i can see progress, will update you. – Abhimanyu Jun 18 '19 at 12:05
  • FWIW; that actually doesn't matter here; StartNew starts a job running on another thread in the threadpool. The delegate passed in is not an async method so can't (and shouldn't) use `await`. Unless you change the delegate to be async. But, point is, it's not actually relevant as you jumping to another thread. – flytzen Jun 18 '19 at 12:16
  • Remove of the System.Thread, highlighted that it didn't work as expected. That is, we are not passing something that returns a Task, just a delegate - hence this will not have the behaviour the op expected. – Ralph Willgoss Jun 19 '19 at 08:49