H
hynek.cihlar
A strange behaviour thatI found in ASP.NET 2.0.
I am trying to issue a callback request (handled by
ICallbackEventHandler and RaiseCallbackEvent) and a regular GET request
in the client browser and handle them at the same time - in parallel.
The funny thing is that the behaviour of the implementation I created
depends on the existence of Global.asax in the Web application.
Here is the source of the page that handles the regular GET request.
public partial class _Default : System.Web.UI.Page,
ICallbackEventHandler
{
protected void Page_Load(object sender, EventArgs e)
{
this.ClientScript.RegisterStartupScript(this.GetType(),
"ClientCallback", "<script type=\"text/javascript\">function
ClientCallback() { };</script>");
Button button = new Button();
button.Text = "Click";
button.UseSubmitBehavior = false;
string callbackcall =
ClientScript.GetCallbackEventReference(this, "", "ClientCallback", "",
false);
button.OnClientClick = "javascript:
window.location='Download.aspx'; " + callbackcall + "; return false;";
PlaceHolder1.Controls.Add(button);
}
public void RaiseCallbackEvent(string eventArgument)
{
FileStream file = new FileStream("c:\\test.bin",
FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite);
StreamWriter writer = new StreamWriter(file);
writer.WriteLine("dummy text data");
writer.Close();
}
public string GetCallbackResult()
{
return "";
}
}
And here is the page that handles the callback request.
public partial class Download : Page
{
protected delegate void AsyncTaskDelegate();
private byte[] fileData = null;
private AsyncTaskDelegate dlgt = null;
protected void Page_Load(object sender, EventArgs e)
{
PageAsyncTask getPdfData = new PageAsyncTask(new
BeginEventHandler(this.BeginGetData), new
EndEventHandler(this.EndGetData), new
EndEventHandler(this.TimeoutGetData), null, true);
Page.RegisterAsyncTask(getPdfData);
Page.ExecuteRegisteredAsyncTasks();
}
private void TimeoutGetData(IAsyncResult ar)
{
dlgt.EndInvoke(ar);
}
private void EndGetData(IAsyncResult ar)
{
dlgt.EndInvoke(ar);
}
private IAsyncResult BeginGetData(object sender, EventArgs e,
AsyncCallback cb, object extraData)
{
dlgt = new AsyncTaskDelegate(this.ExecuteGetData);
IAsyncResult result = dlgt.BeginInvoke(cb, extraData);
return result;
}
private void ExecuteGetData()
{
for (int i = 0; i < 10; i++)
{
if (!File.Exists("c:\\test.bin"))
{
Thread.Sleep(2000);
continue;
}
FileStream file = new FileStream("c:\\test.bin",
FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
fileData = new byte[file.Length];
file.Read(fileData, 0, fileData.Length);
file.Close();
}
}
protected override void Render(HtmlTextWriter writer)
{
if (this.fileData == null) return;
Response.Clear();
Response.ContentType = "application/download";
Response.AddHeader("Content-Disposition", "attachment;
filename=test.bin");
Response.OutputStream.Write(this.fileData, 0,
this.fileData.Length);
Response.End();
}
}
The code above is quite obvious. The _Default page creates a button
with onclick JavaScript handler that creates GET request using the the
window.location and a callback using the standard ASP.NET mechanism.
When the GET request gets handled in Download page it prepares data for
the callback handler in _Default page.
As I formentioned, when there is no Global.asax in the web application,
the code works as one would expect. However, when I create a new
Global.asax in the web application (and leave it empty), the code
breaks.
With Global.asax, I never achieved both requests to be handled at the
same time - in parallel. They are handled one after the other - first
the GET in Download - after it "timeouts" and the handlers exit, the
callback handler in _Default gets executed.
I took two callstack snapshots - both in the Raisecallback handler of
the _Default page. First snapshot with the Global.asax in the web
application and the second without.
With Global.asax:
callbackControlID = "__Page") + 0xbb bytes
System.Web.dll!System.Web.UI.Page.ProcessRequestMain(bool
includeStagesBeforeAsyncPoint = true, bool includeStagesAfterAsyncPoint
= true) + 0x1085 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequest(bool
includeStagesBeforeAsyncPoint = true, bool includeStagesAfterAsyncPoint
= true) + 0x70 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequest() + 0x71 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext
context = {System.Web.HttpContext}) + 0x26 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequest(System.Web.HttpContext
context = {System.Web.HttpContext}) + 0x88 bytes
App_Web_kizvwxal.dll!ASP.default_aspx.ProcessRequest(System.Web.HttpContext
context = {System.Web.HttpContext}) + 0x30 bytes C#
System.Web.dll!System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
+ 0x192 bytes
System.Web.dll!System.Web.HttpApplication.ExecuteStep(System.Web.HttpApplication.IExecutionStep
step = {System.Web.HttpApplication.CallHandlerExecutionStep}, ref bool
completedSynchronously = true) + 0x76 bytes
System.Web.dll!System.Web.HttpApplication.ResumeSteps(System.Exception
error = null) + 0x1c6 bytes
System.Web.dll!System.Web.HttpApplication.ResumeStepsFromThreadPoolThread(System.Exception
error = null) + 0x34 bytes
System.Web.dll!System.Web.HttpApplication.AsyncEventExecutionStep.ResumeStepsWithAssert(System.Exception
error = null) + 0x37 bytes
System.Web.dll!System.Web.HttpApplication.AsyncEventExecutionStep.OnAsyncEventCompletion(System.IAsyncResult
ar = {System.Web.HttpAsyncResult}) + 0xbf bytes
System.Web.dll!System.Web.HttpAsyncResult.Complete(bool synchronous =
false, object result = null, System.Exception error = null) + 0x46
bytes
System.Web.dll!System.Web.SessionState.SessionStateModule.PollLockedSessionCallback(object
state = 1) + 0x18b bytes
mscorlib.dll!System.Threading._TimerCallback.TimerCallback_Context(object
state) + 0x1a bytes
mscorlib.dll!System.Threading.ExecutionContext.runTryCode(object
userData) + 0x43 bytes
[Native to Managed Transition]
[Managed to Native Transition]
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext
executionContext, System.Threading.ContextCallback callback, object
state) + 0xa7 bytes
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext
executionContext, System.Threading.ContextCallback callback, object
state) + 0x92 bytes
mscorlib.dll!System.Threading._TimerCallback.PerformTimerCallback(object
state) + 0x5b bytes
[Appdomain Transition]
Without Global.asax:
callbackControlID = "__Page") + 0xbb bytes
System.Web.dll!System.Web.UI.Page.ProcessRequestMain(bool
includeStagesBeforeAsyncPoint = true, bool includeStagesAfterAsyncPoint
= true) + 0x1086 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequest(bool
includeStagesBeforeAsyncPoint = true, bool includeStagesAfterAsyncPoint
= true) + 0x70 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequest() + 0x71 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext
context = {System.Web.HttpContext}) + 0x26 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequest(System.Web.HttpContext
context = {System.Web.HttpContext}) + 0x88 bytes
App_Web_vv-y2doi.dll!ASP.default_aspx.ProcessRequest(System.Web.HttpContext
context = {System.Web.HttpContext}) + 0x30 bytes C#
System.Web.dll!System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
+ 0x192 bytes
System.Web.dll!System.Web.HttpApplication.ExecuteStep(System.Web.HttpApplication.IExecutionStep
step = {System.Web.HttpApplication.CallHandlerExecutionStep}, ref bool
completedSynchronously = true) + 0x76 bytes
System.Web.dll!System.Web.HttpApplication.ResumeSteps(System.Exception
error = null) + 0x1c6 bytes
System.Web.dll!System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(System.Web.HttpContext
context = {System.Web.HttpContext}, System.AsyncCallback cb =
{System.AsyncCallback}, object extraData = {System.Web.HttpContext}) +
0xa1 bytes
System.Web.dll!System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest
wr = {Microsoft.VisualStudio.WebHost.Request}) + 0x1aa bytes
System.Web.dll!System.Web.HttpRuntime.ProcessRequestNow(System.Web.HttpWorkerRequest
wr = {Microsoft.VisualStudio.WebHost.Request}) + 0x21 bytes
System.Web.dll!System.Web.HttpRuntime.ProcessRequestNoDemand(System.Web.HttpWorkerRequest
wr = {Microsoft.VisualStudio.WebHost.Request}) + 0x51 bytes
System.Web.dll!System.Web.HttpRuntime.ProcessRequest(System.Web.HttpWorkerRequest
wr = {Microsoft.VisualStudio.WebHost.Request}) + 0x63 bytes
WebDev.WebHost.dll!Microsoft.VisualStudio.WebHost.Request.Process() +
0x114 bytes
WebDev.WebHost.dll!Microsoft.VisualStudio.WebHost.Host.ProcessRequest(Microsoft.VisualStudio.WebHost.Connection
conn = {System.Runtime.Remoting.Proxies.__TransparentProxy}) + 0x5c
bytes
[Appdomain Transition]
WebDev.WebHost.dll!Microsoft.VisualStudio.WebHost.Server.OnSocketAccept(object
acceptedSocket) + 0x86 bytes
mscorlib.dll!System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(object
state) + 0x2f bytes
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext
executionContext, System.Threading.ContextCallback callback, object
state) + 0x81 bytes
mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(object
state) + 0x6c bytes
It boils down that the problem is caused by a colision of the mechanism
ASP.NET uses when dispatching requests in web applicatons with
Global.asax and the asynchronous tasks used in the Download handlers.
Note that when the asynchronous task mechanism is not used in the
Download page, everything works flawlessly.
There it is I got so far. If anyone had any ideas I would really
appreciate it.
Hynek
I am trying to issue a callback request (handled by
ICallbackEventHandler and RaiseCallbackEvent) and a regular GET request
in the client browser and handle them at the same time - in parallel.
The funny thing is that the behaviour of the implementation I created
depends on the existence of Global.asax in the Web application.
Here is the source of the page that handles the regular GET request.
public partial class _Default : System.Web.UI.Page,
ICallbackEventHandler
{
protected void Page_Load(object sender, EventArgs e)
{
this.ClientScript.RegisterStartupScript(this.GetType(),
"ClientCallback", "<script type=\"text/javascript\">function
ClientCallback() { };</script>");
Button button = new Button();
button.Text = "Click";
button.UseSubmitBehavior = false;
string callbackcall =
ClientScript.GetCallbackEventReference(this, "", "ClientCallback", "",
false);
button.OnClientClick = "javascript:
window.location='Download.aspx'; " + callbackcall + "; return false;";
PlaceHolder1.Controls.Add(button);
}
public void RaiseCallbackEvent(string eventArgument)
{
FileStream file = new FileStream("c:\\test.bin",
FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite);
StreamWriter writer = new StreamWriter(file);
writer.WriteLine("dummy text data");
writer.Close();
}
public string GetCallbackResult()
{
return "";
}
}
And here is the page that handles the callback request.
public partial class Download : Page
{
protected delegate void AsyncTaskDelegate();
private byte[] fileData = null;
private AsyncTaskDelegate dlgt = null;
protected void Page_Load(object sender, EventArgs e)
{
PageAsyncTask getPdfData = new PageAsyncTask(new
BeginEventHandler(this.BeginGetData), new
EndEventHandler(this.EndGetData), new
EndEventHandler(this.TimeoutGetData), null, true);
Page.RegisterAsyncTask(getPdfData);
Page.ExecuteRegisteredAsyncTasks();
}
private void TimeoutGetData(IAsyncResult ar)
{
dlgt.EndInvoke(ar);
}
private void EndGetData(IAsyncResult ar)
{
dlgt.EndInvoke(ar);
}
private IAsyncResult BeginGetData(object sender, EventArgs e,
AsyncCallback cb, object extraData)
{
dlgt = new AsyncTaskDelegate(this.ExecuteGetData);
IAsyncResult result = dlgt.BeginInvoke(cb, extraData);
return result;
}
private void ExecuteGetData()
{
for (int i = 0; i < 10; i++)
{
if (!File.Exists("c:\\test.bin"))
{
Thread.Sleep(2000);
continue;
}
FileStream file = new FileStream("c:\\test.bin",
FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
fileData = new byte[file.Length];
file.Read(fileData, 0, fileData.Length);
file.Close();
}
}
protected override void Render(HtmlTextWriter writer)
{
if (this.fileData == null) return;
Response.Clear();
Response.ContentType = "application/download";
Response.AddHeader("Content-Disposition", "attachment;
filename=test.bin");
Response.OutputStream.Write(this.fileData, 0,
this.fileData.Length);
Response.End();
}
}
The code above is quite obvious. The _Default page creates a button
with onclick JavaScript handler that creates GET request using the the
window.location and a callback using the standard ASP.NET mechanism.
When the GET request gets handled in Download page it prepares data for
the callback handler in _Default page.
As I formentioned, when there is no Global.asax in the web application,
the code works as one would expect. However, when I create a new
Global.asax in the web application (and leave it empty), the code
breaks.
With Global.asax, I never achieved both requests to be handled at the
same time - in parallel. They are handled one after the other - first
the GET in Download - after it "timeouts" and the handlers exit, the
callback handler in _Default gets executed.
I took two callstack snapshots - both in the Raisecallback handler of
the _Default page. First snapshot with the Global.asax in the web
application and the second without.
With Global.asax:
System.Web.dll!System.Web.UI.Page.PrepareCallback(stringApp_Web_kizvwxal.dll!_Default.RaiseCallbackEvent(string eventArgument = "") Line 39 C#
callbackControlID = "__Page") + 0xbb bytes
System.Web.dll!System.Web.UI.Page.ProcessRequestMain(bool
includeStagesBeforeAsyncPoint = true, bool includeStagesAfterAsyncPoint
= true) + 0x1085 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequest(bool
includeStagesBeforeAsyncPoint = true, bool includeStagesAfterAsyncPoint
= true) + 0x70 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequest() + 0x71 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext
context = {System.Web.HttpContext}) + 0x26 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequest(System.Web.HttpContext
context = {System.Web.HttpContext}) + 0x88 bytes
App_Web_kizvwxal.dll!ASP.default_aspx.ProcessRequest(System.Web.HttpContext
context = {System.Web.HttpContext}) + 0x30 bytes C#
System.Web.dll!System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
+ 0x192 bytes
System.Web.dll!System.Web.HttpApplication.ExecuteStep(System.Web.HttpApplication.IExecutionStep
step = {System.Web.HttpApplication.CallHandlerExecutionStep}, ref bool
completedSynchronously = true) + 0x76 bytes
System.Web.dll!System.Web.HttpApplication.ResumeSteps(System.Exception
error = null) + 0x1c6 bytes
System.Web.dll!System.Web.HttpApplication.ResumeStepsFromThreadPoolThread(System.Exception
error = null) + 0x34 bytes
System.Web.dll!System.Web.HttpApplication.AsyncEventExecutionStep.ResumeStepsWithAssert(System.Exception
error = null) + 0x37 bytes
System.Web.dll!System.Web.HttpApplication.AsyncEventExecutionStep.OnAsyncEventCompletion(System.IAsyncResult
ar = {System.Web.HttpAsyncResult}) + 0xbf bytes
System.Web.dll!System.Web.HttpAsyncResult.Complete(bool synchronous =
false, object result = null, System.Exception error = null) + 0x46
bytes
System.Web.dll!System.Web.SessionState.SessionStateModule.PollLockedSessionCallback(object
state = 1) + 0x18b bytes
mscorlib.dll!System.Threading._TimerCallback.TimerCallback_Context(object
state) + 0x1a bytes
mscorlib.dll!System.Threading.ExecutionContext.runTryCode(object
userData) + 0x43 bytes
[Native to Managed Transition]
[Managed to Native Transition]
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext
executionContext, System.Threading.ContextCallback callback, object
state) + 0xa7 bytes
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext
executionContext, System.Threading.ContextCallback callback, object
state) + 0x92 bytes
mscorlib.dll!System.Threading._TimerCallback.PerformTimerCallback(object
state) + 0x5b bytes
[Appdomain Transition]
Without Global.asax:
System.Web.dll!System.Web.UI.Page.PrepareCallback(stringApp_Web_vv-y2doi.dll!_Default.RaiseCallbackEvent(string eventArgument = "") Line 39 C#
callbackControlID = "__Page") + 0xbb bytes
System.Web.dll!System.Web.UI.Page.ProcessRequestMain(bool
includeStagesBeforeAsyncPoint = true, bool includeStagesAfterAsyncPoint
= true) + 0x1086 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequest(bool
includeStagesBeforeAsyncPoint = true, bool includeStagesAfterAsyncPoint
= true) + 0x70 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequest() + 0x71 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequestWithNoAssert(System.Web.HttpContext
context = {System.Web.HttpContext}) + 0x26 bytes
System.Web.dll!System.Web.UI.Page.ProcessRequest(System.Web.HttpContext
context = {System.Web.HttpContext}) + 0x88 bytes
App_Web_vv-y2doi.dll!ASP.default_aspx.ProcessRequest(System.Web.HttpContext
context = {System.Web.HttpContext}) + 0x30 bytes C#
System.Web.dll!System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
+ 0x192 bytes
System.Web.dll!System.Web.HttpApplication.ExecuteStep(System.Web.HttpApplication.IExecutionStep
step = {System.Web.HttpApplication.CallHandlerExecutionStep}, ref bool
completedSynchronously = true) + 0x76 bytes
System.Web.dll!System.Web.HttpApplication.ResumeSteps(System.Exception
error = null) + 0x1c6 bytes
System.Web.dll!System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(System.Web.HttpContext
context = {System.Web.HttpContext}, System.AsyncCallback cb =
{System.AsyncCallback}, object extraData = {System.Web.HttpContext}) +
0xa1 bytes
System.Web.dll!System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest
wr = {Microsoft.VisualStudio.WebHost.Request}) + 0x1aa bytes
System.Web.dll!System.Web.HttpRuntime.ProcessRequestNow(System.Web.HttpWorkerRequest
wr = {Microsoft.VisualStudio.WebHost.Request}) + 0x21 bytes
System.Web.dll!System.Web.HttpRuntime.ProcessRequestNoDemand(System.Web.HttpWorkerRequest
wr = {Microsoft.VisualStudio.WebHost.Request}) + 0x51 bytes
System.Web.dll!System.Web.HttpRuntime.ProcessRequest(System.Web.HttpWorkerRequest
wr = {Microsoft.VisualStudio.WebHost.Request}) + 0x63 bytes
WebDev.WebHost.dll!Microsoft.VisualStudio.WebHost.Request.Process() +
0x114 bytes
WebDev.WebHost.dll!Microsoft.VisualStudio.WebHost.Host.ProcessRequest(Microsoft.VisualStudio.WebHost.Connection
conn = {System.Runtime.Remoting.Proxies.__TransparentProxy}) + 0x5c
bytes
[Appdomain Transition]
WebDev.WebHost.dll!Microsoft.VisualStudio.WebHost.Server.OnSocketAccept(object
acceptedSocket) + 0x86 bytes
mscorlib.dll!System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(object
state) + 0x2f bytes
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext
executionContext, System.Threading.ContextCallback callback, object
state) + 0x81 bytes
mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(object
state) + 0x6c bytes
It boils down that the problem is caused by a colision of the mechanism
ASP.NET uses when dispatching requests in web applicatons with
Global.asax and the asynchronous tasks used in the Download handlers.
Note that when the asynchronous task mechanism is not used in the
Download page, everything works flawlessly.
There it is I got so far. If anyone had any ideas I would really
appreciate it.
Hynek