1

根据这篇文章

始终调用 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>

更新:我还应该提供其他信息吗?

4

0 回答 0