C# IOThread

来源:互联网 发布:红色警戒2超级武器知乎 编辑:程序博客网 时间:2024/06/08 06:14

在看微软的ASP.NET - 将 ASP.NET 用作高性能文件下载器 示例里面用到了IO 线程,以前打算自己撸的,这里贴出来 已标记一下:

////////////////////////////////////////////////////    // A delegate's BeginInvoke runs on a "worker thread" inside the CLR ThreadPool.    // This allows you to run a delegate on a "completion port thread" inside the CLR ThreadPool    // instead of a "worker thread" by calling IOBeginInvoke instead of BeginInvoke.    // In a nut shell, this mechanism basically skips the actual system I/O and simply posts a    // completion delegate to the completion port queue so the completion delegate will be     // executed by a "completion port thread" that is working the completion port queue.    //    // Example:    // delegate.BeginInvoke => executes delegate on "worker thread".    // delegate.IOBeginInvoke => executes delegate on "completion port thread".    //     //    // Extremely simplified explanation:    //    // CLR ThreadPool is made up of two pools of threads: "worker threads" and "completion port threads".    //    // Basically you can either queue a user work item which runs the delegate on a "worker thread",    // or queue a native overlapped item which runs the completion delegate on a "completion port thread".    // ThreadPool.QueueUserWorkItem (and delegate.BeginInvoke) => executes delegate on "worker thread".    // ThreadPool.UnsafeQueueNativeOverlapped => executes completion delegate on "completion port thread".    //    //             (CLR ThreadPool)    //               /        \    // [worker threads]      [completion port threads]    //    //  o o  oo  o  oo                    _____________post to queue using ThreadPool.UnsafeQueueNativeOverlapped    //    o o oo o  o                    |             (i.e. PostQueuedCompletionStatus)    //   o  o o  o o                     v    //  oo o  oo  o o                  |  |    //    o  oo oo o                   |  | <----------completion port queue (one per process)    //                                 |__|    //       ^                         oo o    //       |                        o o oo <---------completion port threads (work the completion port queue)    //       |                 (cpu)(cpu)(cpu)(cpu)                            (i.e. GetQueuedCompletionStatus in loop)    //       |                                   //       |                               ^    //       |                               |    //       |                               |    //       |                               |    //       |                               |    //       |    Each individual completion delegate is given to the completion port queue to execute,    //       |    and the "completion port threads" working the completion port queue execute the delegate.    //       |    (This has much less risk of thread explosion because each delegate just gets posted to the    //       |     completion port queue, instead of being given to its own individual thread. Basically    //       |     the queue grows, not the threads.)    //       |    //       |    The completion delegate is supplied in the overlapped structure that is posted to the    //       |    completion port queue.    //       |    //       |    Posting to queue (PostQueuedCompletionStatus) => done by ThreadPool.UnsafeQueueNativeOverlapped.    //       |    Working queue (GetQueuedCompletionStatus in loop) => done by "completion port thread" working queue.    //       |    //       |    // Each individual delegate is given to its own individual "worker thread" to execute,    // and the OS schedules the "worker thread" to run on a cpu.    // (This has the risk of thread explosion because each new delegate executes on its own    //  individual "worker thread". If the number of threads grows to large the entire OS can    //  grind to a hault, with all the memory used and the cpu's spending all their time    //  just trying to schedule the threads to run but not actually doing any work.)    //    ////////////////////////////////////////////////////    public static class IOThread    {        public delegate void ProcessRequestDelegate(HttpContext context);        public class IOAsyncResult : IAsyncResult        {            public object AsyncState { get; set; }            public WaitHandle AsyncWaitHandle { get; set; }            public Delegate AsyncDelegate { get; set; }            public bool CompletedSynchronously { get; set; }            public bool IsCompleted { get; set; }            public Exception Exception { get; set; }        }        unsafe public static IAsyncResult IOBeginInvoke(this ProcessRequestDelegate value, HttpContext context, AsyncCallback callback, object data)        {            ManualResetEvent evt = new ManualResetEvent(false);            IOAsyncResult ar = new IOAsyncResult();            ar.AsyncState = new object[] { context, callback, data };            ar.AsyncDelegate = value;            ar.AsyncWaitHandle = evt;            Overlapped o = new Overlapped(0, 0, IntPtr.Zero, ar);            NativeOverlapped* no = o.Pack(new IOCompletionCallback(ProcessRequestCompletionCallback), data);            ThreadPool.UnsafeQueueNativeOverlapped(no);            return ar;        }        unsafe private static void ProcessRequestCompletionCallback(uint errorCode, uint numBytes, NativeOverlapped* no)        {            try            {                Overlapped o = Overlapped.Unpack(no);                ProcessRequestDelegate d = (ProcessRequestDelegate)((IOAsyncResult)o.AsyncResult).AsyncDelegate;                object[] state = (object[])o.AsyncResult.AsyncState;                HttpContext context = (HttpContext)state[0];                AsyncCallback callback = (AsyncCallback)state[1];                object data = state[2];                try                {                    d(context);                }                catch(Exception ex)                {                    ((IOAsyncResult)o.AsyncResult).Exception = ex;                }                ((IOAsyncResult)o.AsyncResult).IsCompleted = true;                ((ManualResetEvent)o.AsyncResult.AsyncWaitHandle).Set();                if (callback != null)                    callback(o.AsyncResult);            }            finally            {                Overlapped.Free(no);            }        }        unsafe public static void IOEndInvoke(this ProcessRequestDelegate value, IAsyncResult result)        {            IOAsyncResult ar = (IOAsyncResult)result;            ar.AsyncWaitHandle.WaitOne(Timeout.Infinite);            if (ar.Exception != null)                throw ar.Exception;        }    }

使用示例:

  ////////////////////////////////////////////////////    // Example URLs to call download handler and download a file:    //    //    https://localhost/DownloadPortal/Download?file=file.txt    //    https://localhost/DownloadPortal/Download?file=file.txt&chunksize=1000000    //    https://localhost/DownloadPortal/Download?file=customer1/file.txt    //    https://localhost/DownloadPortal/Download?file=customer1/file.txt&chunksize=1000000    //    //    Important!!!  Must replace 'localhost' in URL with actual machine name or SSL certificate will fail.    //    //    Can either include the 'Range' HTTP request header in the HTTP web request OR    //    supply the 'chunksize' parameter in the URL. If include the 'Range' HTTP request header,    //    it will transmit back that portion of the file identified by the 'Range'. If supply the    //    'chunksize' parameter, it will transmit back the entire file in separate pieces the size of    //    'chunksize'. If supply both, 'Range' will be used. If supply neither, it will transmit back    //    the entire file in one piece.    //    // Web.config on IIS 7.0:    //    <system.webServer>    //      <handlers>    //        <add name="Download" verb="*" path="Download" type="DownloadHandlers.DownloadHandler" />    //      </handlers>    //    </system.webServer>    //    // Put DownloadHandler.dll and IOThreads.dll in the /bin directory of the virtual directory on IIS.    //    // Put files to be downloaded into virtual directory or sub directory under virtual directory.    //    // To debug:    //    Debug \ Attach to Process \ w3wp.exe    //    // Note: Make sure the IIS virtual directory is using an AppPool that is using .NET Framework 4.0.    //       Define a new AppPool using .NET Framework 4.0, if no other AppPool exists that is using .NET Framework 4.0.    ////////////////////////////////////////////////////    public class DownloadHandler : IHttpAsyncHandler    {        public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)        {            //Offloads to "completion port threads" inside CLR ThreadPool            //instead of "worker threads" inside CLR ThreadPool, so that our            //work doesn't use up the "worker threads" that are needed for the            //IIS server's request processing. (see comment section at top of            //IOThread class for more details)            IOThread.ProcessRequestDelegate d = ProcessRequest;            return d.IOBeginInvoke(context, cb, extraData);        }        public void EndProcessRequest(IAsyncResult result)        {            IOThread.IOAsyncResult ar = (IOThread.IOAsyncResult)result;            IOThread.ProcessRequestDelegate d = (IOThread.ProcessRequestDelegate)ar.AsyncDelegate;            d.IOEndInvoke(result);        }        public void ProcessRequest(HttpContext context)        {            try            {                string file = context.Request.QueryString["File"];                if (string.IsNullOrEmpty(file))                    throw new Exception("Must specify file in query string.  (Example: Download?File=your file)");                string fileName = Path.GetFileName(file);                if (string.IsNullOrEmpty(fileName))                    throw new Exception("File name '" + fileName + "' is not valid.");                long chunkSize = 0;                if (!string.IsNullOrEmpty(context.Request.QueryString["ChunkSize"]))                    if (context.Request.QueryString["ChunkSize"].Trim().Length > 0)                        chunkSize = long.Parse(context.Request.QueryString["ChunkSize"]);                FileInfo fi = new FileInfo(context.Server.MapPath(file));                long fileLength = fi.Length;                if (chunkSize > 0 && chunkSize > fileLength)                    throw new Exception("ChunkSize is greater than file length.");                context.Response.ClearHeaders();                context.Response.ClearContent();                context.Response.Clear();                //request is just checking file length                if (context.Request.HttpMethod == "HEAD")                {                    context.Response.AddHeader("content-length", fileLength.ToString());                    context.Response.AddHeader("accept-ranges", "bytes");                    context.Response.Flush();                    return;                }                                //file save                context.Response.ContentEncoding = Encoding.UTF8;                context.Response.AddHeader("content-disposition", "attachment;filename=\"" + fileName + "\"");                //context.Response.AddHeader("content-disposition", "attachment;filename=\"" + HttpUtility.UrlEncode(fileName) + "\"");                context.Response.ContentType = "application/octet-stream";                context.Response.AddHeader("cache-control", "no-store, no-cache");                context.Response.ExpiresAbsolute = DateTime.Now.Subtract(new TimeSpan(1, 0, 0, 0));                context.Response.Expires = -1;                context.Response.AddHeader("Connection", "Keep-Alive");                context.Response.AddHeader("accept-ranges", "bytes");                //request is for byte range                if (!string.IsNullOrEmpty(context.Request.Headers["Range"]) && context.Request.Headers["Range"].Trim().Length > 0)                {                    string range = context.Request.Headers["Range"];                    range = range.Replace(" ", "");                    string[] parts = range.Split(new char[] { '=', '-' });                    long contentLength = long.Parse(parts[2]) - long.Parse(parts[1]);                    context.Response.AddHeader("content-length", contentLength.ToString());                    string contentRange = string.Format("bytes {0}-{1}/{2}", parts[1], parts[2], fileLength.ToString());                    context.Response.AddHeader("content-range", contentRange);                    context.Response.AddHeader("last-modified", DateTime.Now.ToString("ddd, dd MMM yyyy hh:mm:ss") + " GMT");                    byte[] bytes = Encoding.ASCII.GetBytes(string.Format("{0} : {1}", fi.Name, fi.LastAccessTimeUtc));                    string eTag = Convert.ToBase64String(MD5CryptoServiceProvider.Create().ComputeHash(bytes));                    context.Response.AddHeader("ETag", string.Format("\"{0}\"", eTag));                    context.Response.StatusCode = 206;  //partial content                    context.Response.TransmitFile(file, long.Parse(parts[1]), contentLength);                    context.Response.Flush();                }                else  //request is not for byte range                {                    context.Response.AddHeader("content-length", fileLength.ToString());                    if (chunkSize <= 0)                    {                        context.Response.TransmitFile(file);                        context.Response.Flush();                    }                    else                    {                        long index = 0;                        while (true)                        {                            if (index >= fileLength)                                break;                            if (index < fileLength && index + chunkSize > fileLength)                            {                                context.Response.TransmitFile(file, index, fileLength - index);                                context.Response.Flush();                                break;                            }                            context.Response.TransmitFile(file, index, chunkSize);                            context.Response.Flush();                            index += chunkSize;                        }                    }                }            }            catch (Exception ex)            {                context.Response.ClearHeaders();                context.Response.ClearContent();                context.Response.Clear();                context.Response.StatusCode = 500;                context.Response.Write("Download Error:  " + ex.GetBaseException().Message);                context.Response.Flush();                //throw ex;            }            finally            {                context.Response.End();                //context.ApplicationInstance.CompleteRequest();            }        }        public bool IsReusable        {            get { return false; }        }    }




原创粉丝点击