Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC progress bar won't work correctly

I am trying to make progress bar. On the server side I created a controller, which should get the progress value and store the value in session.

On the client side I created two ajax requests. One of them starts a function, which I want to be monitoring, and the other one just checks the progress stage. I mean, that's how I thought it would work. But it only outputs something when it's done.

It just waits for few seconds and then alerts "Done!" and that's all.

What's the problem? Maybe I should to create new thread in the controller to monitoring the progress stage?

Here's the client side code:

function generate() {

    setTimeout(checkProgress, 400);
        $.ajax({
            type: "POST",
            url: "/PPTReport/Generate",
            async: true,
            success: function (text) {
                 console.log(text);

            },
            error:function() {
                 console.log("error! error!");
            }
        });

  }

function checkProgress() {

    var id = "reportProgress";

    $.ajax({
        type: "POST",
        url: "/PPTReport/GetProgress",
        async: true,
        data: "id="+id,
        success: function (data) {
            if (data.done) {
                 console.log('Done!');
            } else {
                 console.log('Progress at ' + data.percent + '%');
                console.log(data.percent);
                setTimeout(checkProgress, 100 );
            }

        },
        error: function() {
            console.log('ajax error');
        }
    });
}

Here's the server side code

public class ProgressInfo
    {
        public int Percent { get; set; }
        public bool Done { get; set; }
    }

 public JsonResult GetProgress(string id)
        {
            ProgressInfo progress;
            if (string.IsNullOrEmpty(id)
                || (progress = Session[id] as ProgressInfo) == null)
            {
                return Json(new
                {
                    success = false
                });
            }
            if (progress.Done)
            {
                Session.Remove(id);
            }
            return Json(new
                {
                    success = true,
                    done = progress.Done,
                    percent = progress.Percent
                }
            );
        }


public JsonResult Generate(){
                    //init stuff 
                    //there are somtheing like that
                    ProgressInfo progress = new ProgressInfo();
                    progress.Percent = 0;
                    progress.Done = false;
                    if (tabs > 0)
                    {
                        FirstPage();
                        progress.Percent++;
                        Session["reportProgress"] = progress;
                        if (tabs > 1)
                        {
                            SecondPage();
                            progress.Percent++;
                            Session["reportProgress"] = progress;
                            if (tabs > 2)
                            {
                                ThirdPage();
                                progress.Percent++;
                                Session["reportProgress"] = progress;
                                if (tabs > 3)
                                {
                                   LastPage();
                                }
                             }
                         }
                     }
                   //what we've gonna return stuff etc 
         }

UPD: well, I finally kinda make it - I made test function (almost like in example) Here's the code:

js:

 function doCalculation() {
            $.post('/PPTReport/DoCalculation/sas');
            setTimeout(pollProgress, 1000);
        }

        function pollProgress() {
            var progressID = "sas";
            $.post('/PPTReport/GetProgress/' + progressID, function (response) {
                if (!response.success) {
                    alert('Cannot find progress');
                    return;
                }
                if (response.done) {
                    alert('Done!');
                } else {
                    alert('Progress at ' + response.percent + '%');
                    setTimeout(pollProgress, 1000);
                }
            }, 'json');

}

server-side:

public void DoCalculation(string id)
        {
            ProgressInfo progress = new ProgressInfo();
            if (!string.IsNullOrEmpty(id))
            {
                Session[id] = progress;
            }

            //periodicly update progress
            int i = 0;
            while(i == 0 && progress.Percent != 7600000340)
            {
                progress.Percent++;
                Thread.Sleep(1000);
            }
        }

        public JsonResult GetProgress(string id)
        {
            ProgressInfo progress;
            if (string.IsNullOrEmpty(id)
                || (progress = Session[id] as ProgressInfo) == null)
            {
                return Json(new
                {
                    success = false
                });
            }
            if (progress.Done)
            {
                Session.Remove(id);
            }
            return Json(new
            {
                success = true,
                done = progress.Done,
                percent = progress.Percent
            });
        }

but I wait more than a minute until the action will execute for the first time! Look enter image description here

Why does it happen? It just calling simple function, why does it wait so long?

like image 499
DanilGholtsman Avatar asked Jul 21 '14 03:07

DanilGholtsman


2 Answers

This is an instance of an old problem, see here:

http://msdn.microsoft.com/en-us/library/ms178581.aspx

Access to ASP.NET session state is exclusive per session, which means that if two different users make concurrent requests, access to each separate session is granted concurrently. However, if two concurrent requests are made for the same session (by using the same SessionID value), the first request gets exclusive access to the session information. The second request executes only after the first request is finished. (The second session can also get access if the exclusive lock on the information is freed because the first request exceeds the lock time-out.) If the EnableSessionState value in the @ Page directive is set to ReadOnly, a request for the read-only session information does not result in an exclusive lock on the session data. However, read-only requests for session data might still have to wait for a lock set by a read-write request for session data to clear.

I think you can find numerous posts on SO that discuss this. Here is one:

Asynchronous Controller is blocking requests in ASP.NET MVC through jQuery

and here is one workaround:

Disable Session state per-request in ASP.Net MVC

Ok adding some (untested!) example code. This is just to indicate how to solve this:

public class myCache {

    private static Dictionary<string, ProgressInfo> _pinfos = new Dictionary<string, ProgressInfo>();
    private static readonly object _lockObject = new object();

    public static void Add(string key, ProgressInfo value)
    {
        lock (_lockObject)
        {
            if (!_pinfos.ContainsKey(key)) {
                _pinfos.Add(key, value);
            }
            else {
               _pinfos[key] = value;
            }

        }
    }

    public static ProgressInfo Get(string key) {
        if (_pinfos.ContainsKey(key)) {
          return _pinfos[key];
        }
        else {
          return null;
        }
    }

    public static void Remove(string key) {         
        lock (_lockObject)
        {
          if (_pinfos.ContainsKey(key)) {
             _pinfos.Remove(key);
          }
        }
    }
}




[SessionState(SessionStateBehavior.ReadOnly)]
public class TestController : AsyncController

    public void DoCalculation(string id)
        {
            ProgressInfo progress = new ProgressInfo();
            if (!string.IsNullOrEmpty(id)) {
                myCache.Add(id,progress);
            }

            //periodicly update progress
            int i = 0;
            while(i == 0 && progress.Percent != 7600000340)
            {
                progress.Percent++;
                Thread.Sleep(1000);
            }
        }

        public JsonResult GetProgress(string id)
        {
            ProgressInfo progress;
            if (string.IsNullOrEmpty(id)
                || (progress = myCache.Get(id)) == null)
            {
                return Json(new
                {
                    success = false
                });
            }
            if (progress.Done)
            {
                myCache.Remove(id);
            }
            return Json(new
            {
                success = true,
                done = progress.Done,
                percent = progress.Percent
            });
        }
}
like image 195
skarist Avatar answered Nov 15 '22 05:11

skarist


Well Skarist explained WHY it's happening and what I was running into as well. I made an (incorrect) assumption that changing the Session State to read only would prevent me from writing to the Session, but it doesn't.

What I did to solve my problem is set the EnableSessionState to "ReadOnly" on the page I was assigning the session values in as well. This basically makes the Session not thread safe and allowed me to pull the values when I polled the server. I'm using WebForms for this at the moment, but it should be similar in MVC using [SessionState(SessionStateBehavior.Disabled)].

like image 21
Matti Price Avatar answered Nov 15 '22 04:11

Matti Price