The Labs \ Source Viewer \ SSCLI \ System.Threading \ OverlappedDataCacheLine

  1. // ==++==
  2. //
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. //
  14. // ==--==
  15. /*=============================================================================
  16. **
  17. ** Class: Overlapped
  18. **
  19. **
  20. ** Purpose: Class for converting information to and from the native
  21. **          overlapped structure used in asynchronous file i/o
  22. **
  23. **
  24. =============================================================================*/
  25. namespace System.Threading
  26. {
  27.     using System;
  28.     using System.Runtime.InteropServices;
  29.     using System.Runtime.CompilerServices;
  30.     using System.Security;
  31.     using System.Security.Permissions;
  32.     using System.Runtime.ConstrainedExecution;
  33.    
  34.     // Valuetype that represents the (unmanaged) Win32 OVERLAPPED structure
  35.     // the layout of this structure must be identical to OVERLAPPED.
  36.     // The first five matches OVERLAPPED structure.
  37.     // The remaining are reserved at the end
  38.     [System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential)]
  39.     [System.Runtime.InteropServices.ComVisible(true)]
  40.     public struct NativeOverlapped
  41.     {
  42.         public IntPtr InternalLow;
  43.         public IntPtr InternalHigh;
  44.         public int OffsetLow;
  45.         public int OffsetHigh;
  46.         public IntPtr EventHandle;
  47.     }
  48.    
  49.     unsafe internal class _IOCompletionCallback
  50.     {
  51.         IOCompletionCallback _ioCompletionCallback;
  52.         ExecutionContext _executionContext;
  53.         uint _errorCode;
  54.         // Error code
  55.         uint _numBytes;
  56.         // No. of bytes transferred
  57.         NativeOverlapped* _pOVERLAP;
  58.        
  59.         internal _IOCompletionCallback(IOCompletionCallback ioCompletionCallback, ref StackCrawlMark stackMark)
  60.         {
  61.             _ioCompletionCallback = ioCompletionCallback;
  62.             // clone the exection context
  63.             _executionContext = ExecutionContext.Capture(ref stackMark);
  64.             ExecutionContext.ClearSyncContext(_executionContext);
  65.         }
  66.         // Context callback: same sig for SendOrPostCallback and ContextCallback
  67.         static internal ContextCallback _ccb = new ContextCallback(IOCompletionCallback_Context);
  68.         static internal void IOCompletionCallback_Context(object state)
  69.         {
  70.             _IOCompletionCallback helper = (_IOCompletionCallback)state;
  71.             BCLDebug.Assert(helper != null, "_IOCompletionCallback cannot be null");
  72.             helper._ioCompletionCallback(helper._errorCode, helper._numBytes, helper._pOVERLAP);
  73.         }
  74.        
  75.        
  76.         // call back helper
  77.             // Error code
  78.             // No. of bytes transferred
  79.         // ptr to OVERLAP structure
  80.         unsafe static internal void PerformIOCompletionCallback(uint errorCode, uint numBytes, NativeOverlapped* pOVERLAP)
  81.         {
  82.             Overlapped overlapped = OverlappedData.GetOverlappedFromNative(pOVERLAP).m_overlapped;
  83.             _IOCompletionCallback helper = overlapped.iocbHelper;
  84.            
  85.             if (helper == null || helper._executionContext == null || helper._executionContext.IsDefaultFTContext()) {
  86.                 // We got here because of UnsafePack (or) Pack with EC flow supressed
  87.                 IOCompletionCallback callback = overlapped.UserCallback;
  88.                 callback(errorCode, numBytes, pOVERLAP);
  89.             }
  90.             else {
  91.                 // We got here because of Pack
  92.                 helper._errorCode = errorCode;
  93.                 helper._numBytes = numBytes;
  94.                 helper._pOVERLAP = pOVERLAP;
  95.                 ExecutionContext.Run(helper._executionContext.CreateCopy(), _ccb, helper);
  96.             }
  97.         }
  98.     }
  99.    
  100.     internal sealed class OverlappedData : CriticalFinalizerObject
  101.     {
  102.         // ! If you make any change to the layout here, you need to make matching change
  103.         // ! to OverlappedObject in vm\nativeoverlapped.h
  104.         internal IAsyncResult m_asyncResult;
  105.         internal IOCompletionCallback m_iocb;
  106.         internal _IOCompletionCallback m_iocbHelper;
  107.         internal Overlapped m_overlapped;
  108.         private object m_userObject;
  109.         internal OverlappedDataCacheLine m_cacheLine;
  110.         private IntPtr m_pinSelf;
  111.         private IntPtr m_userObjectInternal;
  112.         private int m_AppDomainId;
  113.         internal short m_slot;
  114.         #pragma warning disable 414 // Field is not used from managed.
  115.         #pragma warning disable 169
  116.         private byte m_isArray;
  117.         private byte m_toBeCleaned;
  118.         #pragma warning restore 414
  119.         #pragma warning restore 169
  120.         internal NativeOverlapped m_nativeOverlapped;
  121.        
  122.         internal OverlappedData(OverlappedDataCacheLine cacheLine)
  123.         {
  124.             m_cacheLine = cacheLine;
  125.         }
  126.        
  127.         ~OverlappedData()
  128.         {
  129.             if (null != m_cacheLine && false == m_cacheLine.Removed) {
  130.                 // If user drops reference to Overlapped before calling Pack,
  131.                 // let us return the entry to cache
  132.                 if (!Environment.HasShutdownStarted && !AppDomain.CurrentDomain.IsFinalizingForUnload()) {
  133.                     // If user drops reference to OverlappedData before pack,
  134.                     // we return the object to cache.
  135.                     // We try to keep all items in cache until the whole cache line is not needed.
  136.                     OverlappedDataCache.CacheOverlappedData(this);
  137.                     GC.ReRegisterForFinalize(this);
  138.                 }
  139.             }
  140.         }
  141.        
  142.         internal void ReInitialize()
  143.         {
  144.             m_asyncResult = null;
  145.             m_iocb = null;
  146.             m_iocbHelper = null;
  147.             m_overlapped = null;
  148.             m_userObject = null;
  149.             BCLDebug.Assert(m_pinSelf.IsNull(), "OverlappedData has not been freed: m_pinSelf");
  150.             m_pinSelf = (IntPtr)0;
  151.             m_userObjectInternal = (IntPtr)0;
  152.             BCLDebug.Assert(m_AppDomainId == 0 || m_AppDomainId == AppDomain.CurrentDomain.Id, "OverlappedData is not in the current domain");
  153.             m_AppDomainId = 0;
  154.             m_nativeOverlapped.EventHandle = (IntPtr)0;
  155.             m_isArray = 0;
  156.             m_nativeOverlapped.InternalHigh = (IntPtr)0;
  157.         }
  158.        
  159.         unsafe internal NativeOverlapped* Pack(IOCompletionCallback iocb, object userData)
  160.         {
  161.             if (!m_pinSelf.IsNull()) {
  162.                 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_Overlapped_Pack"));
  163.             }
  164.             StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
  165.            
  166.             if (iocb != null) {
  167.                 m_iocbHelper = new _IOCompletionCallback(iocb, ref stackMark);
  168.                 m_iocb = iocb;
  169.             }
  170.             else {
  171.                 m_iocbHelper = null;
  172.                 m_iocb = null;
  173.             }
  174.             m_userObject = userData;
  175.             if (m_userObject != null) {
  176.                 if (m_userObject.GetType() == typeof(object[])) {
  177.                     m_isArray = 1;
  178.                 }
  179.                 else {
  180.                     m_isArray = 0;
  181.                 }
  182.             }
  183.             return AllocateNativeOverlapped();
  184.         }
  185.        
  186.         [SecurityPermissionAttribute(SecurityAction.LinkDemand, ControlEvidence = true, ControlPolicy = true)]
  187.         unsafe internal NativeOverlapped* UnsafePack(IOCompletionCallback iocb, object userData)
  188.         {
  189.             if (!m_pinSelf.IsNull()) {
  190.                 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_Overlapped_Pack"));
  191.             }
  192.             m_userObject = userData;
  193.             if (m_userObject != null) {
  194.                 if (m_userObject.GetType() == typeof(object[])) {
  195.                     m_isArray = 1;
  196.                 }
  197.                 else {
  198.                     m_isArray = 0;
  199.                 }
  200.             }
  201.             m_iocb = iocb;
  202.             m_iocbHelper = null;
  203.             return AllocateNativeOverlapped();
  204.         }
  205.        
  206.         [ComVisible(false)]
  207.         internal IntPtr UserHandle {
  208.             get { return m_nativeOverlapped.EventHandle; }
  209.             set { m_nativeOverlapped.EventHandle = value; }
  210.         }
  211.        
  212.         [MethodImplAttribute(MethodImplOptions.InternalCall)]
  213.         unsafe private extern NativeOverlapped* AllocateNativeOverlapped();
  214.        
  215.         [MethodImplAttribute(MethodImplOptions.InternalCall)]
  216.         unsafe static internal extern void FreeNativeOverlapped(NativeOverlapped* nativeOverlappedPtr);
  217.        
  218.         [MethodImplAttribute(MethodImplOptions.InternalCall)]
  219.         unsafe static internal extern OverlappedData GetOverlappedFromNative(NativeOverlapped* nativeOverlappedPtr);
  220.     }
  221.    
  222.     /// <internalonly/>
  223.     [System.Runtime.InteropServices.ComVisible(true)]
  224.     public class Overlapped
  225.     {
  226.         private OverlappedData m_overlappedData;
  227.        
  228.         public Overlapped()
  229.         {
  230.             m_overlappedData = OverlappedDataCache.GetOverlappedData(this);
  231.         }
  232.        
  233.         public Overlapped(int offsetLo, int offsetHi, IntPtr hEvent, IAsyncResult ar)
  234.         {
  235.             m_overlappedData = OverlappedDataCache.GetOverlappedData(this);
  236.             m_overlappedData.m_nativeOverlapped.OffsetLow = offsetLo;
  237.             m_overlappedData.m_nativeOverlapped.OffsetHigh = offsetHi;
  238.             m_overlappedData.UserHandle = hEvent;
  239.             m_overlappedData.m_asyncResult = ar;
  240.         }
  241.        
  242.         [Obsolete("This constructor is not 64-bit compatible. Use the constructor that takes an IntPtr for the event handle. http://go.microsoft.com/fwlink/?linkid=14202")]
  243.         public Overlapped(int offsetLo, int offsetHi, int hEvent, IAsyncResult ar) : this(offsetLo, offsetHi, new IntPtr(hEvent), ar)
  244.         {
  245.         }
  246.        
  247.         public IAsyncResult AsyncResult {
  248.             get { return m_overlappedData.m_asyncResult; }
  249.             set { m_overlappedData.m_asyncResult = value; }
  250.         }
  251.        
  252.         public int OffsetLow {
  253.             get { return m_overlappedData.m_nativeOverlapped.OffsetLow; }
  254.             set { m_overlappedData.m_nativeOverlapped.OffsetLow = value; }
  255.         }
  256.        
  257.         public int OffsetHigh {
  258.             get { return m_overlappedData.m_nativeOverlapped.OffsetHigh; }
  259.             set { m_overlappedData.m_nativeOverlapped.OffsetHigh = value; }
  260.         }
  261.        
  262.         [Obsolete("This property is not 64-bit compatible. Use EventHandleIntPtr instead. http://go.microsoft.com/fwlink/?linkid=14202")]
  263.         public int EventHandle {
  264.             get { return m_overlappedData.UserHandle.ToInt32(); }
  265.             set { m_overlappedData.UserHandle = new IntPtr(value); }
  266.         }
  267.        
  268.         [ComVisible(false)]
  269.         public IntPtr EventHandleIntPtr {
  270.             get { return m_overlappedData.UserHandle; }
  271.             set { m_overlappedData.UserHandle = value; }
  272.         }
  273.        
  274.         internal _IOCompletionCallback iocbHelper {
  275.             get { return m_overlappedData.m_iocbHelper; }
  276.         }
  277.        
  278.         internal IOCompletionCallback UserCallback {
  279.             get { return m_overlappedData.m_iocb; }
  280.         }
  281.        
  282. /*====================================================================
  283.         *  Packs a managed overlapped class into native Overlapped struct.
  284.         *  Roots the iocb and stores it in the ReservedCOR field of native Overlapped
  285.         *  Pins the native Overlapped struct and returns the pinned index.
  286.         ====================================================================*/       
  287.         [Obsolete("This method is not safe. Use Pack (iocb, userData) instead. http://go.microsoft.com/fwlink/?linkid=14202")]
  288.         [CLSCompliant(false)]
  289.         unsafe public NativeOverlapped* Pack(IOCompletionCallback iocb)
  290.         {
  291.             return Pack(iocb, null);
  292.         }
  293.        
  294.         [CLSCompliant(false), ComVisible(false)]
  295.         unsafe public NativeOverlapped* Pack(IOCompletionCallback iocb, object userData)
  296.         {
  297.             return m_overlappedData.Pack(iocb, userData);
  298.         }
  299.        
  300.         [Obsolete("This method is not safe. Use UnsafePack (iocb, userData) instead. http://go.microsoft.com/fwlink/?linkid=14202")]
  301.         [CLSCompliant(false)]
  302.         [SecurityPermissionAttribute(SecurityAction.LinkDemand, ControlEvidence = true, ControlPolicy = true)]
  303.         unsafe public NativeOverlapped* UnsafePack(IOCompletionCallback iocb)
  304.         {
  305.             return UnsafePack(iocb, null);
  306.         }
  307.        
  308.         [CLSCompliant(false), ComVisible(false)]
  309.         [SecurityPermissionAttribute(SecurityAction.LinkDemand, ControlEvidence = true, ControlPolicy = true)]
  310.         unsafe public NativeOverlapped* UnsafePack(IOCompletionCallback iocb, object userData)
  311.         {
  312.             return m_overlappedData.UnsafePack(iocb, userData);
  313.         }
  314.        
  315. /*====================================================================
  316.         *  Unpacks an unmanaged native Overlapped struct.
  317.         *  Unpins the native Overlapped struct
  318.         ====================================================================*/       
  319.         [CLSCompliant(false)]
  320.         unsafe public static Overlapped Unpack(NativeOverlapped* nativeOverlappedPtr)
  321.         {
  322.             if (nativeOverlappedPtr == null)
  323.                 throw new ArgumentNullException("nativeOverlappedPtr");
  324.            
  325.             Overlapped overlapped = OverlappedData.GetOverlappedFromNative(nativeOverlappedPtr).m_overlapped;
  326.            
  327.             return overlapped;
  328.         }
  329.        
  330.         [CLSCompliant(false)]
  331.         unsafe public static void Free(NativeOverlapped* nativeOverlappedPtr)
  332.         {
  333.             if (nativeOverlappedPtr == null)
  334.                 throw new ArgumentNullException("nativeOverlappedPtr");
  335.            
  336.             Overlapped overlapped = OverlappedData.GetOverlappedFromNative(nativeOverlappedPtr).m_overlapped;
  337.             OverlappedData.FreeNativeOverlapped(nativeOverlappedPtr);
  338.             OverlappedData overlappedData = overlapped.m_overlappedData;
  339.             overlapped.m_overlappedData = null;
  340.             OverlappedDataCache.CacheOverlappedData(overlappedData);
  341.         }
  342.        
  343.     }
  344.    
  345.     // We try to reuse all items in a cache line.
  346.     // When the cache line is not needed, we release all items associated.
  347.     internal sealed class OverlappedDataCacheLine
  348.     {
  349.         internal OverlappedData[] m_items;
  350.         internal OverlappedDataCacheLine m_next;
  351.         private bool m_removed;
  352.         internal const short CacheSize = 16;
  353.        
  354.         internal OverlappedDataCacheLine()
  355.         {
  356.             m_items = new OverlappedData[OverlappedDataCacheLine.CacheSize];
  357.             // Allocate some dummy objects before and after the cacheLine.
  358.             // These objects will allow GC to move two cacheLine's closer.
  359.             new object();
  360.             for (short i = 0; i < OverlappedDataCacheLine.CacheSize; i++) {
  361.                 m_items[i] = new OverlappedData(this);
  362.                 m_items[i].m_slot = i;
  363.             }
  364.             new object();
  365.         }
  366.        
  367.         ~OverlappedDataCacheLine()
  368.         {
  369.             m_removed = true;
  370.         }
  371.        
  372.         internal bool Removed {
  373.             get { return m_removed; }
  374.             set { m_removed = value; }
  375.         }
  376.     }
  377.    
  378.     internal sealed class OverlappedDataCache : CriticalFinalizerObject
  379.     {
  380.         // OverlappedData will be pinned during async io operation.
  381.         // In order to avoid pinning in gen 0, we use a cache to recycle OverlappedData.
  382.         private static OverlappedDataCacheLine m_overlappedDataCache;
  383.         private static int m_overlappedDataCacheAccessed;
  384.         private static int m_cleanupObjectCount;
  385.         private static float m_CleanupThreshold;
  386.         private const float m_CleanupStep = 0.05f;
  387.         private const float m_CleanupInitialThreadhold = 0.3f;
  388.        
  389.         private int m_gen2GCCount;
  390.         private bool m_ready;
  391.        
  392.         //static private int m_CollectionCount;
  393.        
  394. /*
  395.         static private int m_Create = 0;
  396.         static private int m_Total = 0;
  397.         static private int m_Cache = 0;
  398.         */       
  399.        
  400.         // Setup the cache.
  401.         private static void GrowOverlappedDataCache()
  402.         {
  403.             OverlappedDataCacheLine data = new OverlappedDataCacheLine();
  404.             if (m_overlappedDataCache == null) {
  405.                 // Add the first node in the list
  406.                 if (Interlocked.CompareExchange<OverlappedDataCacheLine>(ref m_overlappedDataCache, data, null) == null) {
  407.                     // Use GC to remove items.
  408.                     new OverlappedDataCache();
  409.                     return;
  410.                 }
  411.             }
  412.            
  413.             // If there is already a first node in the list we'll add the new OverlappedDataCacheLine at the end
  414.             if (m_cleanupObjectCount == 0) {
  415.                 new OverlappedDataCache();
  416.             }
  417.            
  418.             while (true) {
  419.                 // Chain the new node
  420.                 OverlappedDataCacheLine walk = m_overlappedDataCache;
  421.                 while (null != walk && null != walk.m_next) {
  422.                     // There's a race with the finalizer here between testing if walk.m_next is null and assigning
  423.                     // Note 1.
  424.                     // If walk has been removed from the list by the finalizer thread we still have a valid
  425.                     // walk.next chain that will eventually lead us back to the original list, or to NULL.
  426.                     // If NULL see Note 4 below.
  427.                     // Note 2.
  428.                     // If walk.next was removed from list the next time through the loop we'll be in the
  429.                     // situation described in Note 1.
  430.                     // Note 3.
  431.                     // If walk.next was set to NULL by the finalizer thread we'll need to test for that in the
  432.                     // while condition and after exiting the loop.
  433.                     walk = walk.m_next;
  434.                 }
  435.                 // if walk has become null (due to finalizer race) after the while test
  436.                 // simply return and let GetOverlappedData retry!
  437.                 if (null == walk)
  438.                     return;
  439.                
  440.                 // Add the new OverlappedDataCacheLine at the end of the list.
  441.                 // Note 4.
  442.                 // Even if the node that walk points to has been removed from the list we simply
  443.                 // add the new node to an unreachable graph. GetOverlappedData() will notice that
  444.                 // there are still no empty OverlappedData elements and call us again. The
  445.                 // "unreachable list" will be reclaimed during the next GC.
  446.                 if (Interlocked.CompareExchange<OverlappedDataCacheLine>(ref walk.m_next, data, null) == null)
  447.                     break;
  448.             }
  449.         }
  450.        
  451.        
  452.         static internal OverlappedData GetOverlappedData(Overlapped overlapped)
  453.         {
  454.             OverlappedData overlappedData = null;
  455.            
  456.             Interlocked.Exchange(ref m_overlappedDataCacheAccessed, 1);
  457.            
  458.             while (true) {
  459.                 OverlappedDataCacheLine walk = m_overlappedDataCache;
  460.                 while (null != walk) {
  461.                     for (short i = 0; i < OverlappedDataCacheLine.CacheSize; i++) {
  462.                         if (walk.m_items[i] != null) {
  463.                             overlappedData = Interlocked.Exchange<OverlappedData>(ref walk.m_items[i], null);
  464.                             if (overlappedData != null) {
  465.                                 overlappedData.m_overlapped = overlapped;
  466.                                 return overlappedData;
  467.                             }
  468.                         }
  469.                     }
  470.                    
  471.                     walk = walk.m_next;
  472.                 }
  473.                
  474.                 GrowOverlappedDataCache();
  475.             }
  476.            
  477.             /*
  478.             Interlocked.Increment(ref m_Total);
  479.             Console.WriteLine("OverlappedDataCache get " + m_Total +
  480.                               " create " + m_Create +
  481.                               " Cache " + m_Cache);
  482.             */           
  483.         }
  484.        
  485.         // Return a free OverlappedData to cache if the cache has slot available.
  486.         static internal void CacheOverlappedData(OverlappedData data)
  487.         {
  488.             data.ReInitialize();
  489.            
  490.             data.m_cacheLine.m_items[data.m_slot] = data;
  491.         }
  492.        
  493.         internal OverlappedDataCache()
  494.         {
  495.             if (m_cleanupObjectCount == 0) {
  496.                 m_CleanupThreshold = m_CleanupInitialThreadhold;
  497.                 if (Interlocked.Exchange(ref m_cleanupObjectCount, 1) == 0) {
  498.                     m_ready = true;
  499.                 }
  500.             }
  501.         }
  502.        
  503.         // Per GC, if the cache has not been accessed, remove some items from cache.
  504.         ~OverlappedDataCache()
  505.         {
  506.             if (!m_ready) {
  507.                 return;
  508.             }
  509.            
  510.             if (null == m_overlappedDataCache) {
  511.                 Interlocked.Exchange(ref m_cleanupObjectCount, 0);
  512.                 return;
  513.             }
  514.            
  515.             if (!Environment.HasShutdownStarted && !AppDomain.CurrentDomain.IsFinalizingForUnload()) {
  516.                 GC.ReRegisterForFinalize(this);
  517.             }
  518.            
  519.             int gen2GCCount = GC.CollectionCount(GC.MaxGeneration);
  520.             if (gen2GCCount == m_gen2GCCount) {
  521.                 // Only do the cleanup when a Gen2 GC happens.
  522.                 return;
  523.             }
  524.            
  525.             m_gen2GCCount = gen2GCCount;
  526.            
  527.             // reclaim cache
  528.             OverlappedDataCacheLine prev = null;
  529.             OverlappedDataCacheLine walk = m_overlappedDataCache;
  530.             OverlappedDataCacheLine empty = null;
  531.             OverlappedDataCacheLine preempty = prev;
  532.             int total = 0;
  533.             int used = 0;
  534.             while (walk != null) {
  535.                 total++;
  536.                 bool fUsed = false;
  537.                 for (short i = 0; i < OverlappedDataCacheLine.CacheSize; i++) {
  538.                     if (walk.m_items[i] == null) {
  539.                         fUsed = true;
  540.                         used++;
  541.                     }
  542.                 }
  543.                 if (!fUsed) {
  544.                     preempty = prev;
  545.                     empty = walk;
  546.                 }
  547.                 prev = walk;
  548.                 walk = walk.m_next;
  549.             }
  550.             total *= OverlappedDataCacheLine.CacheSize;
  551.            
  552.             if (null != empty && total * m_CleanupThreshold > used) {
  553.                 // We can remove one cache line
  554.                 // We only remove a cache line if it is empty.
  555.                 if (preempty == null) {
  556.                     m_overlappedDataCache = empty.m_next;
  557.                 }
  558.                 else {
  559.                     preempty.m_next = empty.m_next;
  560.                 }
  561.                
  562.                 empty.Removed = true;
  563.             }
  564.            
  565.             if (m_overlappedDataCacheAccessed != 0) {
  566.                 m_CleanupThreshold = m_CleanupInitialThreadhold;
  567.                 Interlocked.Exchange(ref m_overlappedDataCacheAccessed, 0);
  568.             }
  569.             else {
  570.                 m_CleanupThreshold += m_CleanupStep;
  571.             }
  572.             /*
  573.             OverlappedData[] cache1 = m_overlappedDataCache;
  574.             int length1 = cache1.Length;
  575.             int count1 = 0;
  576.             for (int i1 = 0; i1 < length1; i1 ++)
  577.             {
  578.                 if (cache1[i1] != null)
  579.                 {
  580.                     count1 ++;
  581.                 }
  582.             }
  583.             Console.WriteLine("cleanup left " + count1);
  584.             */           
  585.         }
  586.     }
  587. }

Developer Fusion