In Asp.net Web Form app, I have Generate Report button and a Cancel button to cancel report generation process, if it takes a long time.
When I Click Generate Report it does the heavy task, after 3 seconds I try to cancel this heavy task by clicking the Cancel button.
But the server side code for Cancel button click is called after some time delay.
I even tried window.stop()
in JavaScript
to stop page loading and hit server code fast, but still, there is a delay.
Code:
protected void btnExportExcel_Click(object sender, EventArgs e) { // Doing Heavy Task to Generate Report } protected void btnCancel_Click(object sender, EventArgs e) { if (Response.IsClientConnected) { HttpContext.Current.ApplicationInstance.CompleteRequest(); } } <asp:Button ID="btnCancel" runat="server" Text="Cancel Request" OnClientClick="return StopPageLoading();" OnClick="btnCancel_Click" /> function StopPageLoading() { try { window.stop(); } catch (exception) { document.execCommand('Stop'); // for IE and other browsers } }
How can I allow to start another request on click fast, while current request is in processing?
How to allow UI
to be responsive?
Update:
To reproduce the situation:
- I click Export To Excel , it takes 7 minutes to process.
- While still processing I click Cancel button , it takes another 5 minutes to cancel.
I read that Concurrent request is not possible in Asp.NET because of session state makes exclusive locks.
So How to cancel fast ?
Will making my methods async
help to overcome session state exclusive locks issue ?
4 Answers
Answers 1
As in another question from you, you can do it this way
public partial class Contact : Page { private static CancellationTokenSource tokenSource = new CancellationTokenSource(); protected void Page_Load(object sender, EventArgs e) { } protected async void btnExportExcel_Click(object sender, EventArgs e) { CancellationToken cToken = tokenSource.Token; cToken.Register(() => cancelNotification()); try { await Task.Run(() => { cToken.ThrowIfCancellationRequested(); GenerateReport(sender, cToken); }, cToken); } catch(OperationCanceledException) { } } private void GenerateReport(object sender, CancellationToken ct) { // Just to Simulate Export to excel Thread.Sleep(70000); } protected void btnCancel_Click(object sender, EventArgs e) { tokenSource.Cancel(); } private static void cancelNotification() { // Do your stuff when the user is canceld the report generation } }
And in your Page.aspx
Need something like this
<%@ Page Async="true" EnableSessionState="false" %>
I hope this helps you!
Answers 2
Though, you can do it or not (I am not sure), it is generally not a good idea to create a long running task within ASP.NET, especially if they are background tasks. So there are some helper libraries that are for this purpose.
- HangFire http://docs.hangfire.io Hangfire allows you to kick off method calls outside of the request processing pipeline in a very easy, but reliable way. These method invocations are performed in a background thread and called background jobs. this support using cancellation tokens to cancel a background job
- Quarz.NET I have never tryed Quarz.NET
- Or you have to do it yourself. I know some projecs that take some dedicate servers just for run background and time consuming tasks. So the website is just a frontend layer that set some flags that control the task is cancel or not
Answers 3
The problem is in IIS (or any other Web Server) architecture. The server returns a response to a client (browser) and forget it. Your first request (btnExcelExport_Click
) must be finished before it returns response to the browser. The click on btnCancel
starts a new request which knows nothing about the first one.
The workaround is to send a long job to another thread
. It is possible to communicate to that thread using state object
sent to the new thread. Something like this.
<%@ Page Language="C#" %> <!DOCTYPE html> <script runat="server"> protected void btnRun_Click(object sender, EventArgs e) { var jobState = new StateInfo() { Id = 1, Counter = 0, Content = "Start the job", Cancelled = false, Completed = false }; Session["job"] = jobState; //Save state between requests System.Threading.ThreadPool.QueueUserWorkItem( new System.Threading.WaitCallback(LongJob), jobState );//returns immediately lblState.Text += "<br />" + jobState.Counter.ToString() + " Completed: " + jobState.Completed.ToString() + " Cancelled: " + jobState.Cancelled.ToString() + "<br />" + jobState.Content; btnCancel.Visible = true; btnCheck.Visible = true; } protected void btnCancel_Click(object sender, EventArgs e) { var jobState = Session["job"] as StateInfo; if (!jobState.Completed) jobState.Cancelled = true; System.Threading.Thread.Sleep(1000);//wait for the next loop to complete lblState.Text += "<br />" + jobState.Counter.ToString() + " Completed: " + jobState.Completed.ToString() + " Cancelled: " + jobState.Cancelled.ToString() + (jobState.Completed || jobState.Cancelled ? "<br />" + jobState.Content : ""); } protected void btnCheck_Click(object sender, EventArgs e) { var jobState = Session["job"] as StateInfo; lblState.Text += "<br />" + jobState.Counter.ToString() + " Completed: " + jobState.Completed.ToString() + " Cancelled: " + jobState.Cancelled.ToString() + (jobState.Completed || jobState.Cancelled ? "<br />" + jobState.Content : ""); } private void LongJob(object state) { var jobState = state as StateInfo; do { System.Threading.Thread.Sleep(1000); jobState.Counter++; if (jobState.Counter >= 100) { jobState.Completed = true; jobState.Content = "The job is completed"; } else if (jobState.Cancelled) jobState.Content = "The job is cancelled"; } while (!jobState.Cancelled && !jobState.Completed); } [Serializable] class StateInfo { public int Id { get; set; } public int Counter { get; set; } public string Content { get; set; } public bool Cancelled { get; set; } public bool Completed { get; set; } } </script> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>Long Job</title> </head> <body> <form id="form1" runat="server"> <div> <asp:Label ID="lblState" runat="server"></asp:Label><br /> <asp:Button runat="server" ID="btnRun" OnClick="btnRun_Click" Text="Run" /> <asp:Button runat="server" ID="btnCheck" OnClick="btnCheck_Click" Text="Check State" Visible="false" /> <asp:Button runat="server" ID="btnCancel" OnClick="btnCancel_Click" Text="Cancel" Visible="false" /> </div> </form> </body> </html>
Answers 4
Did you try using the async/await operation in the code instead of synchronous
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
when the report generation call is asynchronous then redirect to UI once the report generation process is kicked off. Now the cancel button can be clicked when needed.
Does this help?
0 comments:
Post a Comment