Update text in TextBox from Async button click - asynchronous

I have a process that takes a long time to finish executing, the user should be able to see a simple feedback when the process starts and finishes
something like this:
protected async void Button1_Click(object sender, EventArgs e)
{
TextBox1.Text = "Process started..\n";
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
await ProcessDelay();
sw.Stop();
TextBox1.Text += "Process finished.\n";
TextBox1.Text += "Elapsed Time (ms): " + sw.ElapsedMilliseconds.ToString() + "\n";
}
private async Task ProcessDelay()
{
await Task.Delay(5000);
}
Text value in TextBox is not updated until the execution of ProcessDelay() is done. What am I missing here?

Remember that when you're working with ASP.NET, you are working within a strict request+response paradigm. WebForms tries to hide this from you, but it's still one-response-per-request underneath. So, when you click a button in your browser, it sends a request to the web server, which executes the click code, returning a result. It can only return one result. So there's no way it can, say, make a change to part of the page and then later make another change.
To put it another way, async on ASP.NET yields to the ASP.NET runtime (that is, it returns the request thread to the thread pool). It does not yield to the browser (that is, it does not return a response).
To do what you want, you'll need an alternative technology. If the background work doesn't take too long, you could consider SignalR. Otherwise, you'll probably need a proper distributed architecture: a reliable queue connected to an independent background process. I describe a few approaches on my blog.

Related

My Windows application freezes using Background worker threads

I have written an application that uses background workers for long running tasks. At times, after the task is completed, the application will freeze. It doesn't do it right away, it will do it after the application sits idle for a little bit of time.
To try to find out where it is hanging, in my development environment I ran it and waited for it to freeze. I then went to Debug > Break All. It is hanging in the Main() method in Program.cs:
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Main());
}
}
The Application.Run line is highlighted as where the application is hung. When I hover my cursor over the carat in the left border I get a tool tip saying "This is the next statement to execute when this thread returns from the current function."
In looking at this code I realized that it is calling the "main" form of the application, which I named "Main." So my first question is does this matter since the current method is named "Main" also? If so, what are the ramifications of renaming the form, if that is possible?
If that is not an issue, then it would go back to the background worker I would imagine. The application never freezes if those long running tasks are never ran. I know that you should never try to access the UI thread from a background worker thread and I don't think I'm doing that but here is some code that hopefully someone may spot something:
First I start the thread from the UI thread passing in an argument:
bgwInternal.RunWorkerAsync(clients)
In the DoWork method it calculates and creates invoices for the passed in argument (clients). It creates PDF files and saves them to disk. None of that work tries to access the UI. It does use the ProgressChanged event handler to update a progress bar and a label in the UI:
private void bgwInternal_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pgbProgress.Value = e.ProgressPercentage;
lblProgress.Text = e.ProgressPercentage.ToString();
}
And finally the RunWorkerCompleted event handler:
private void bgwInternal_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show("Error occurred during invoice creation.\n\r\n\rError Message: " + e.Error.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else if (!e.Cancelled)
{
MessageBox.Show("Invoice Creation Complete", "Complete", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
MessageBox.Show("Invoice Creation Cancelled", "Cancelled", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
btnCreateInv.Enabled = true;
btnClose.Enabled = true;
btnCancel.Enabled = false;
}
Could it be hanging because I'm accessing UI elements in this event handler?
One final note, I was using Application.DoEvents():
while (bgwInternal.IsBusy)
{
Application.DoEvents();
}
But I commented that out to see if it would make a difference and it did not.
Not having a lot of multithreading experience I chose to use background worker threads because they are simple and straightforward. Other than using Debug > Break All I really don't know how to track down the exact reason this is happening.
Any thoughts / ideas would be greatly appreciated.
Thanks.

Page.AsyncTimeout - endless timeout?

I saw an example of forever iframe implementation ( comet simulation ) , so I decided to test it but with the addition of asynchronous approach , so that there will be no blocking.
Pretty simple :
I have a page (index.html) with hidden iframe which has SRC of AdminPush.aspx:
/*1*/ protected void Page_Load(object sender, EventArgs e)
/*2*/ {
/*3*/ UpdateMessage();
/*4*/ }
/*5*/
/*6*/
/*7*/ protected void UpdateMessage()
/*8*/ {
/*9*/ HttpContext.Current.Response.ContentType = "text/html";
/*10*/ Response.Write("<script >parent.UpdateMessage(DateTime.Now.Second)</script>");
/*11*/ Response.Flush();
/*12*/
/*13*/ //async part goes here !!
/*14*/ this.RegisterAsyncTask(new PageAsyncTask(async cancellationToken =>
/*15*/ {
/*16*/ await Task.Delay(2000, cancellationToken);
/*17*/ UpdateMessage();
/*18*/ }));
/*19*/ }
On the AdminPush.aspx Page I added :
Async="true"
On the html page (index.html) I added :
function UpdateMessage(Message)
{
console.log(Message);
}
function setupAjax() //body Onload - calls it.
{
var iframe = document.createElement("iframe");
iframe.src = "adminpush.aspx";
iframe.style.display = "none";
document.body.appendChild(iframe);
}
So basically the iframe is being injected with script comands , which updates the parent of the iframe which is index.html.
It is working.
But when I tested it - it stopped updating after 45 seconds.
I thought it had to do with requestTimeout prop in web.config - but it wasnt.
It was related to the missing AsyncTimeOut prop in the AdminPush.aspx page.
Question #1:
According to msdn AsyncTimeout :
Gets or sets a value indicating the time-out interval used when
processing asynchronous tasks.
But it also says :
A TimeSpan that contains the allowed time interval for completion of
the asynchronous task. The default time interval is 45 seconds.
please notice that I "delay" 2 sec every time
at first I set the timeout to 1 minute , but then it failed also. I thought that the timeout should be regarding each operation , and not to sum(all async operations)
Why is it like that ? it suppose to be timeout for async task ! (single) but it behaves as sum(tasks)
The wording here are misleading. any clarification ?
Question #2:
I need to set it to max value. what is that value ? but still , I need it so support a browser for a very long time. so I'm afraid that this value won't help either.
Is there any way I can RESET this value (after n cycles) ?
I know that there are other solutions/libraries like signalR which are doing the job, still, it does not prevent learning how other stuff are done.
The idea of Asynchronous Pages is to free IIS so more users can be served, if you create a page that "never" finishes, you will eat up all your resources.
That been said... if you still want to do it...
We "know" (documentation), Asynchronous Pages work by splitting the execution of the page in 2... everything BEFORE the Background Tasks and everything AFTER the tasks, in that way IIS can process more requests while the background tasks finish their work. (there is more to it, but that is enough for now)
So... they "must" be creating some kind of Task Manager (like a root/main task) that executes all the registered tasks in sequence, in that way IIS starts processing the page, fires up the task manager, frees IIS, the task manager keeps processing the tasks and when it finishes, it returns control to IIS.
That would explain why the AsyncTimeout controls all the registered tasks instead of one-by-one (The timeout is actually applied to the Task Manager).
I tested a variation of your code with a timeout of 6000 seconds and it works:
C#:
protected void Page_Load(object sender, EventArgs e)
{
Page.RegisterAsyncTask(new PageAsyncTask(ProcessTask));
}
protected async Task ProcessTask()
{
await Task.Delay(1000);
Response.Write(DateTime.Now.ToLongTimeString() + "<br/>");
Response.Flush();
Page.RegisterAsyncTask(new PageAsyncTask(ProcessTask));
}
aspx:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Sample03.Default" Async="true" AsyncTimeout="6000" %>
Hope it helps.

How to stop an Loading function on button click?

I have a problem, I have to stop one Loading class on button click. I already checked some forums related to this. But didn't find an exact solution.
For example:
Public Sub LoadDropDown()
Dim it As Integer
For it = 0 To 1000000
DropDownList1.Items.Add(it)
Next
End Sub
I have to load the DropDown on Load button click and I have to cancel that on cancel button click.
Since populating the control happens on the server, I can't imagine way to interrupt your method from the client. The whole control is being populated, rendered, and only then sent to the client. You might interrupt the callback using ajax, but then the control wouldn't be returned at all.
An alternative could be to load the contents in chunks with ajax and append them to the control on the client-side.
There is no formal way to do what you're asking, but you should be able to achieve the same result if you refactor your code. If certain content shouldn't be loaded for certain users, do that logic in your code behind before it renders to the page.
Per your response to the other answers...
You could consider doing multiple my_ddl.items.add() calls on a timer. Would involve multiple, separate postbacks / ajax calls. For example:
1) add records for 2 seconds (instead of a fixed number of records at a time)
2) check for session("continue") = "true"
3) add more records for 2 more seconds
4) check session("continue")
...
At some point, user clicks cancel, which assigns "false" to session("continue"). Next time your loop checks session("continue"), it will see that it's false and exit.
This would give you a partially-loaded data control. You might want other code to wipe-out the partial update.
I think you could accomplish this with a Session Variable. Forgive me, but I'll have to provide the example in C#, but I'm sure you can get the general idea of this.
private bool CancelRequested
{
get
{
if (Session["CancelRequested"] == null)
return false;
else
return (bool)Session["CancelRequested"];
}
set
{
Session["CancelRequested"] = value;
}
}
public void LoadDropDown()
{
for (int it = 0; it <= 1000000; it++)
{
if (CancelRequested)
{
CancelRequested = false;
break;
}
//Your logic here
}
}
protected void btnCancelRequest_Click(object sender, EventArgs e)
{
CancelRequested = true;
}
The idea here is that the inital loop checks a Session variable to see if it should continue or break out of the loop. If you have a button on the page that will allow the user to set this Session variable to "true", they can essential communicate to the inital request and cause it to break out of the loop. I'm not sure if this would fully accomplish what you're looking to achieve, but hopefully it helps.

Response.Redirect() ThreadAbortException Bubbling Too High Intermittently

I understand (now) that Response.Redirect() and Response.End() throw a ThreadAbortException as an expensive way of killing the current processing thread to emulate the behaviour of ASP Classic's Response.End() and Response.Redirect methods.
However.
It seems intermittently in our application that the exception bubbles too high. For example, we have a page that is called from client side javascript to return a small string to display in a page.
protected void Page_Load(object sender, EventArgs e)
{
// Work out some stuff.
Response.Write(stuff);
Response.End();
}
This generally works, but sometimes, we get the exception bubbling up to the UI layer and get part of the exception text displayed in the page.
Similarly, else where we have:
// check the login is still valid:
if(!loggedin) {
Response.Redirect("login.aspx");
}
In some cases, the user is redirected to login.aspx, in others, the user gets an ASP.NET error page and stack dump (because of how our dev servers are configured).
i.e. in some cases, response.redirect throws an exception all the way up INSTEAD of doing a redirect. Why? How do we stop this?
Have you tried overloading the default Redirect method and not ending the response?
if(!loggedin) {
Response.Redirect("login.aspx", false);
}
You can use the following best-practice code instead, as explained by this answer to prevent the exception from happening in the first place:
Response.Redirect(url, false);
Context.ApplicationInstance.CompleteRequest();
Since I was looking for an answer to this question too, I am posting what seams to me a complete solution, rounding up the two above answers:
public static void Redirect(this TemplateControl control, bool ignoreIfInvisible = true)
{
Page page = control.Page;
if (!ignoreIfInvisible || page.Visible)
{
// Sets the page for redirect, but does not abort.
page.Response.Redirect(url, false);
// Causes ASP.NET to bypass all events and filtering in the HTTP pipeline
// chain of execution and directly execute the EndRequest event.
HttpContext.Current.ApplicationInstance.CompleteRequest();
// By setting this to false, we flag that a redirect is set,
// and to not render the page contents.
page.Visible = false;
}
}
Source:
http://www.codeproject.com/Tips/561490/ASP-NET-Response-Redirect-without-ThreadAbortExcep

IHttpModule.BeginRequest firing 2X, Application_BeginRequest firing 1X

I'm running VS 2008 and .NET 3.5 SP1.
I want to implement hit tracking in an HttpModule in my ASP.NET app. Pretty simple, I thought. However, the BeginRequest event of my HttpModule is firing twice for each page hit. The site is very simple right now...no security, just a bit of database work. Should log one row per page hit. Why is this event firing twice?
Moreover, IHttpModule.BeginRequest actually fires a different number of times for the first page hit when running for the first time (from a closed web browser)...3 times when I'm hitting the DB to provide dynamic data for the page, and only 1 time for pages where the DB isn't hit. It fires 2 times for every page hit after the first one, regardless of whether or not I'm touching the DB.
It's interesting to note that Application_BeginRequest (in Global.asax) is always firing only once.
Here's the code:
using System;
using System.Data;
using System.Data.Common;
using System.Net;
using System.Web;
using BluHeron.BusinessLayer;
using Microsoft.Practices.EnterpriseLibrary.Data.Sql;
namespace BluHeron.HttpModules
{
public class SiteUsageModule : IHttpModule
{
public void Init(HttpApplication httpApp)
{
httpApp.BeginRequest += OnBeginRequest;
}
static void OnBeginRequest(object sender, EventArgs a)
{
UsageLogger.LogSiteUsage(((HttpApplication)sender).Context.Request);
}
public void Dispose()
{ }
}
public static class UsageLogger
{
public static void LogSiteUsage(HttpRequest r)
{
string ipAddress = GetHostAddress(Dns.GetHostAddresses(Dns.GetHostName()));
string browserVersion = r.Browser.Type;
string[] urlChunks = r.RawUrl.Split('/');
string page = urlChunks[urlChunks.GetLength(0)-1];
SqlDatabase db = new SqlDatabase(Common.GetConnectionString());
DbCommand cmd = db.GetStoredProcCommand("LogUsage");
db.AddInParameter(cmd, "IPAddress", SqlDbType.NVarChar, ipAddress);
db.AddInParameter(cmd, "BrowserVersion", SqlDbType.NVarChar, browserVersion);
db.AddInParameter(cmd, "PageName", SqlDbType.NVarChar, page);
db.AddInParameter(cmd, "Notes", SqlDbType.NVarChar, "");
db.ExecuteNonQuery(cmd);
}
private static string GetHostAddress(IPAddress[] addresses)
{
foreach (IPAddress ip in addresses)
{
if (ip.ToString().Length <= 15)
{
return ip.ToString();
}
}
return "";
}
}
}
This might be too late for the answer but can be useful for someone else. I faced with the same problem. BeginRequest event triggered for twice for each request. I debugged the code and realized that the first trigger for actual resource request but the second is result of "favicon.ico" request. At the beginning of BeginRequest event, a simple check for favicon.ico request eliminates second execution of the method.
public void Application_BeginRequest(object sender, EventArgs e) {
HttpApplication app = (HttpApplication)sender;
HttpContext ctx = app.Context;
if (ctx.Request.Path == "/favicon.ico") { return; }
quite late on this, but ran into the same issue. In our case it was due to the anonymous request first that returns the 401 per the RFC. The second request authenticates.
The "Default Document" part of IIS seems to fire a second BeginRequest event.
If you have determined that the Request.Path is the same for the HttpApplication in both event handlers and your URL ends with a slash, try adding a URL Rewrite rule to shortcut the "Default Document" processing.
This is interesting. I removed the reference to the CSS file from the master page and I'm getting fewer repeat hits in the HttpModule for certain browsers (as was suggested), but I'm still getting repeats. I have 6 browsers installed, and I'm getting some variation between them.
For reference, this is the URL I'm plugging in to my browsers for this test:
http://localhost/BluHeron
default.aspx is set as the start page and is indeed returned for the aforementioned URL. I'm using HttpRequest.RawUrl for reporting which page the user hit. Specifically, I'm splitting the RawUrl string and just reporting the last item in the array of strings (see code).
Every single browser is reporting hitting default.aspx, as expected (RawUrl = /BluHeron/default.aspx).
4 of the 6 browsers are also reporting BluHeron (RawUrl = /BluHeron).
3 of the 6 browsers are also recording a blank in the database (RawUrl = /BluHeron/).
There are a couple ways I can get accurate reporting of how many people are hitting which pages.
Select from the database only rows that actually list one of my pages (ignore /BluHeron and blanks)
Just use Application_BeginRequest in the global.asax file, which seems to consistently get called only once per page hit.
Get this figured out.
So, I've got options for getting good reports even with crappy data in the database. I would prefer to understand what's going on here and not to have junk in the database.
Thanks for looking, everyone!
We solved this by using
HttpContext.Current.ApplicationInstance.CompleteRequest();
This should prevent the the twice fire you are seeing.
One possibility is that there are other requests going on that you might not be considering. For example, let's say your ASPX page references some images or CSS files. If those requests go through the ASP.NET pipeline then your module will be called and they'll register as hits.
Also, when you say IHttpModule.BeginRequest, do you mean that in IHttpModule.Init() you are hooking up HttpApplication.BeginRequest? If so then the reason I mention above might still apply.
Disable Browser Link in Visual Studio 2013 and up, which causes the second request.
This occurs when an Application is run from Visual Studio.

Resources