根据这篇文章
始终调用 Begin 事件处理程序
我直到最近才真正理解的 AsyncTimeout 的第二个含义是始终调用已注册异步任务的开始事件处理程序,即使页面在 ASP.NET 开始执行该任务之前已经超时。
在这种情况下,剩下的任务基本上就注定了。该页面已经超时,但 ASP.NET 仍将执行为所有已注册任务调用 begin 事件处理程序的动作,然后立即调用其相应的超时事件处理程序。
AsyncTimeout 并不意味着异步任务取消
在可能的情况下,一旦整个页面超时已经触发,我应该注意不要启动其他任务,并且一旦发生超时,我应该取消剩余的正在运行的任务。这是推荐/安全的吗?鉴于异步任务可能在多个位置,我将如何彻底取消任何 HttpWebRequest.BeginGetResponse 调用?例如,如果我在 BeginGetResponse 调用中,我应该返回什么来让 IAsyncResult 安全地停止其轨道中的处理?相同的准则是否适用于 SqlCommand.BeginExecuteReader?
我从几个示例和我自己的补充中整理了一个示例代码:
<%@ Page Language="C#" Async="true" AsyncTimeout="30" %>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.Threading" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
protected class RequestState
{
public HttpWebRequest Request;
public string RequestID; // simple identifier of the request
public string Url;
public DateTime RequestStartTime;
public RequestState(string requestID, string url)
{
this.RequestID = requestID;
this.Url = url;
}
public void CreateRequest()
{
Request = (HttpWebRequest)WebRequest.Create(Url + pageDest);
Request.Method = "GET";
Request.Proxy = WebRequest.DefaultWebProxy;
Request.Timeout = 1000 * 3; // Connection timeout
Request.ReadWriteTimeout = 1000 * 15; // Read response timeout
RequestStartTime = DateTime.Now; // Technically the request didn't start yet, but this is close enough
}
}
protected class ResponseDetails
{
public string RequestID;
public string Url;
public string ServerID;
public double Duration;
public string ResponseString;
public string FormattedDuration
{
get
{
return String.Format("{0:N0}ms", Duration);
}
}
public bool IsServerOk
{
get
{
// trivial check, better check intended for real implementation
return ResponseString.Contains("<body");
}
}
public ResponseDetails(RequestState reqState, string serverID, string responseString)
{
this.RequestID = reqState.RequestID;
this.ServerID = serverID;
this.Url = reqState.Url;
this.ResponseString = responseString;
this.Duration = (DateTime.Now - reqState.RequestStartTime).TotalMilliseconds;
}
}
public const string pageDest = "/";
Dictionary<string, string> serverRequests = new Dictionary<string, string>()
{
{ "dev1", "http://www.yahoo.com" },
{ "dev2", "http://www.msn.com" },
{ "dev3", "http://www.google.com" }
};
Dictionary<string, ResponseDetails> serverResponses = new Dictionary<string, ResponseDetails>();
object dictLock = new object();
int maxIOThreads = 0;
int maxWorkerThreads = 0;
protected bool ShowThreadingInfo = false;
protected bool ShowRequestTime = true;
protected bool ExecuteInParallel = true;
private string GetThreadingCounts()
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat("<b>EndIOCPUpDate {0}</b><br />", DateTime.Now);
/*
sb.AppendFormat("CompletedSynchronously: {0}<br/><br/>" + AR.CompletedSynchronously + "<br /><br />");
sb.AppendFormat("isThreadPoolThread: " + System.Threading.Thread.CurrentThread.IsThreadPoolThread.ToString() + "<br />";
sb.AppendFormat("ManagedThreadId : " + System.Threading.Thread.CurrentThread.ManagedThreadId + "<br />";
sb.AppendFormat("GetCurrentThreadId : " + AppDomain.GetCurrentThreadId() + "<br />";
sb.AppendFormat("Thread.CurrentContext : " + System.Threading.Thread.CurrentContext.ToString() + "<br />";
*/
int availWorker = 0;
int maxWorker = 0;
int availCPT = 0;
int maxCPT = 0;
ThreadPool.GetAvailableThreads(out availWorker, out availCPT);
ThreadPool.GetMaxThreads(out maxWorker, out maxCPT);
if (maxIOThreads < (maxCPT - availCPT))
maxIOThreads = (maxCPT - availCPT);
if (maxWorkerThreads < (maxWorker - availWorker))
maxWorkerThreads = (maxWorker - availWorker);
sb.AppendFormat("--Available Worker Threads: {0}<br/>", availWorker);
sb.AppendFormat("--Maximum Worker Threads: {0}<br/>", maxWorker);
sb.AppendFormat("--Available Completion Port Threads: {0}<br/>", availCPT);
sb.AppendFormat("--Maximum Completion Port Threads: {0}<br/>", maxCPT);
sb.AppendFormat("===========================<br /><br />");
return sb.ToString();
}
protected void Page_Load(object sender, EventArgs e)
{
foreach (KeyValuePair<string, string> kvp in serverRequests)
{
RequestState reqState = new RequestState(kvp.Key, kvp.Value);
Page.RegisterAsyncTask(new PageAsyncTask(new BeginEventHandler(this.BeginGetStatusPage),
new EndEventHandler(this.EndGetStatusPage), new EndEventHandler(this.TimeoutHandler), reqState, ExecuteInParallel));
}
}
protected override void OnPreRenderComplete(EventArgs e)
{
base.OnPreRenderComplete(e);
// write the result messages to the Label.
MessageOut();
}
private IAsyncResult BeginGetStatusPage(Object sender, EventArgs e, AsyncCallback cb, object state)
{
RequestState reqState = (RequestState)state;
AddTraceMessage("Begin " + reqState.Url);
reqState.CreateRequest();
return reqState.Request.BeginGetResponse(cb, reqState);
}
void EndGetStatusPage(IAsyncResult asyncResult)
{
AddTraceMessage("EndAsync");
if (asyncResult != null)
{
RequestState reqState = asyncResult.AsyncState as RequestState;
AddTraceMessage("End " + reqState.Url);
string serverKey = null;
string retString = null;
StringBuilder sb = new StringBuilder();
try
{
using (WebResponse response1 = (WebResponse)reqState.Request.EndGetResponse(asyncResult))
{
AddTraceMessage("End Response " + reqState.Url);
// grab a custom header later, not useful yet
serverKey = response1.Headers["Server"];
// we will read data via the response stream
using (Stream resStream = response1.GetResponseStream())
{
using (StreamReader rdr = new StreamReader(resStream))
{
sb.Append(rdr.ReadToEnd());
rdr.Close();
}
resStream.Close();
}
response1.Close();
}
}
catch (WebException ex)
{
sb.AppendLine(ex.Status.ToString() + ": " + ex.Message);
}
retString = sb.ToString();
AddTraceMessage("End Response2 " + reqState.Url);
ResponseDetails rd = new ResponseDetails(reqState, serverKey, retString);
UpdateServerResponses(rd);
}
}
void UpdateServerResponses(ResponseDetails details)
{
lock (dictLock)
{
serverResponses.Add(details.RequestID, details);
}
}
// This doesn't actually cancel the task I don't think
void TimeoutHandler(IAsyncResult asyncResult)
{
AddTraceMessage("Request Timed Out");
// Aborts one request running during page timeout. What about the rest??
RequestState reqState = asyncResult.AsyncState as RequestState;
reqState.Request.Abort();
ShowErrorDetails("<span style='color:red;font-weight:bold'>Timed out:</span> " + reqState.RequestID + "<br/><br/>");
}
private void ShowErrorDetails(ResponseDetails rd)
{
PanelErrors.Visible = true;
LabelErrors.Text += String.Format("<span style='color:blue;font-weight:bold'>{0}</span><br/>{1}<br/><br/>", rd.RequestID, rd.ResponseString);
}
private void ShowErrorDetails(string message)
{
PanelErrors.Visible = true;
LabelErrors.Text += message;
}
private void MessageOut()
{
PanelThreading.Visible = ShowThreadingInfo;
Page.Trace.Write(_trace.ToString());
foreach (KeyValuePair<string, string> kvp in serverRequests)
{
string respStr = "<font color='red'>No Reply</font>";
if (serverResponses.ContainsKey(kvp.Key))
{
ResponseDetails rd = serverResponses[kvp.Key];
if (rd.IsServerOk)
{
respStr = "OK";
respStr += " (" + rd.ServerID + ")";
}
else
{
ShowErrorDetails(rd);
respStr = "<font color='red'>ERROR</font>";
}
//In parallel task mode it seems to return the time of the single longest individual request. Really weird.
if (ShowRequestTime)
respStr += " [" + rd.FormattedDuration + "]";
}
this.Label1.Text += String.Format("{0}: {1}<br/>", kvp.Key, respStr);
}
this.Label1.Text += String.Format("<br/><b>MaxWorkerThreads:</b> {0}<br/>", maxWorkerThreads);
this.Label1.Text += String.Format("<b>MaxIOThreads:</b> {0}<br/>", maxIOThreads);
}
StringBuilder _trace = new StringBuilder();
DateTime _pageStartTime = DateTime.Now;
public void AddTraceMessage(string message)
{
double t = (DateTime.Now - _pageStartTime).TotalSeconds;
lock (_trace)
{
_trace.AppendFormat("Thread:[{0:000}] {1:00.000} -- {2}\r\n",
System.Threading.Thread.CurrentThread.GetHashCode(), t, message);
LabelThreading.Text += GetThreadingCounts();
}
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label runat="server" ID="Label1"></asp:Label>
<asp:Panel runat="server" ID="PanelErrors" Visible="false">
<br />
<h2>
Error Details:</h2>
<br />
<asp:Label runat="server" ID="LabelErrors"></asp:Label>
</asp:Panel>
<asp:Panel runat="server" ID="PanelThreading" Visible="true">
<br />
<h2>
Threading Details:</h2>
<br />
<asp:Label runat="server" ID="LabelThreading"></asp:Label>
</asp:Panel>
</div>
</form>
</body>
</html>
更新:我还应该提供其他信息吗?