Чистая обработка AsyncTimeout на ASP.NET Async Page - PullRequest
1 голос
/ 03 марта 2010

Согласно этой статье

Обработчик события Begin всегда вызывается

Второе значение AsyncTimeout, что у меня не было до недавнего времени обработчик события начала для зарегистрированное асинхронное задание всегда вызывается, даже если страница была синхронизирована до того, как ASP.NET доберется до запуск этой задачи.

В этом сценарии остаток от задачи в основном обречены. Страница истекло время ожидания, но ASP.NET все еще собираюсь пройти через движения вызова обработчика события начала для все зарегистрированные задачи, следуют немедленно по вызову их соответствующие обработчики событий тайм-аута.

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>

Обновление: есть ли другая информация, которую я должен предоставить?

...