开发者

How to cancel execution of all logic when user leaves a page?

Not sure if this is possible, but when a user leaves a page to go to another page I want all server logic on the current page to end execution. I don't want a user to see an error that came from a page they were previou开发者_如何学编程sly on (e.g. long running SQL query that times out). How would I do this?


How long does it take the execution of your long running query? I would probably focus on optimize that part and the overall design of the application.

if you are loading data in a DataGrid for example and you call the business logic which calls the data layer to load a chunk of data from the database, if the query is fast and your server side does execute the query and meanwhile the user closes the browser or navigates away, the result simply will not be consumed because there is no page to load and render that data anymore.

if you really have loooong lasting server side operations that you would like to control and abort depending on the application navigation I think those operations should not be performed synchronously anyway, I would monitor those asynchronously and simply show the results in a page whenever the user open such page.


You can use the Response.IsClientConnected to know if your client still reading your page.

if (!Response.IsClientConnected)
{
     // Stop
}

I agree that you need to focus to avoid the long running loops that make your user go way, but if you won to see if your user is still on your page you can use the above command. You can call it time to time to check it out.


I was having the same issue with my website http://www.iprotein.info. Basically it runs on a large data set (300 k) from a SQL database and conducts complex calculation which may take time if the query is large. When I put this website online, I found some users issued tasks but decided not to wait for the calculation and left the page, whereas the server kept calculating until all was complete. This caused severe CPU congestion, wasting CPU time on abandoned tasks.

So my solution was:

The controller assigns a unique ID to each new page.

private static ConcurrentDictionary<int, CancellationTokenSource> tasks = new ConcurrentDictionary<int, CancellationTokenSource>();

    private static TaskID taskID = new TaskID();
const int taskCap = 10000;

public IActionResult SearchPage()
        {
            lock (taskID)
            {
                if (taskID.id > taskCap)
                    taskID.id = 0;
                ViewBag.taskID = taskID.id;
                taskID.id++;
            }
            return View();
        }

The client side tells the server which task is going to be terminated by ID:

$(window).unload(function () {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open("GET", '@Url.Action("CancelTask")', true);
    xmlHttp.send(@ViewBag.taskID);
});

Model:

public class TaskID
    {
        public int id;
        public TaskID()
        {
            id = 0;
        }
    }

On the search calls, assign a CancellationTokenSource to allow cancellation of tasks, and store it in a static map with the thread ID. _SearchAction() is the real thing to be done.

[HttpPost]
        public JsonResult SearchAction(string query, int instanceID)
        {           
            lock (tasks)
            {
                tasks[instanceID] = new CancellationTokenSource();
            }
            CancellationToken ct = tasks[instanceID].Token;
            var searchTask = Task.Factory.StartNew(() => _SearchAction(query, instanceID, ct));
            JsonResult res;
            try
            {
                res = searchTask.Result;
            }
            catch (OperationCanceledException)
            {
                res = null;
            }
            lock (tasks)
            {
                CancellationTokenSource temp;
                tasks.Remove(instanceID, out temp);
                temp.Dispose();
            }
            return res;
        }

when receiving the CancelTask call, cancel the Task:

public void CancelTask(int id)
        {
            if (interactionCache.ContainsKey(id))
            {
                List<Tuple<int, int>> list;
                interactionCache.Remove(id, out list);
            }
            if (tasks.ContainsKey(id) && tasks[id] != null)
            {
                tasks[id].Cancel();
            }
        }

It seems to be working so far...

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜