Visual Basic, .NET, ASP, VBScript
 

   
   
     

Форум - Общий форум

Страница: 1 | 2 | 3 |

 

  Вопрос: GC финализирует объект без причины Добавлено: 01.09.12 20:36  

Автор вопроса:  Programmer

Ответить

  Ответы Всего ответов: 45  

Номер ответа: 16
Автор ответа:
 Artyom



Разработчик

Вопросов: 130
Ответов: 6602
 Профиль | | #16 Добавлено: 02.09.12 00:13
EROS пишет:
Объект никем не используется и на него НЕТ ссылок

Я так полагаю, вы хотели сказать, что на него нет ЖИВЫХ ссылок??

EROS пишет:
Я рассчитываю, что, возможно, кто-то сможет предположить, каким образом может происходит вызов ~ObjectName(),

Собственно EROS все верно ответил. Я знаком с принципом работы менеджера памяти дотнета очень хорошо, и не могу придумать ситуацию, в которой бы у реально живого объекта мог бы по какой-либо причине быть вызван финализатор.
К финализатору нельзя получить доступ через Reflection. Через IL Emit, если не ошибаюсь, тоже не получится.

Разве что добраться до MethodTable, оттуда получить адрес метода финализации и вызвать его (впрочем если в вашем коде есть что-то подобное, то странно что глючит только финализация)

Ответить

Номер ответа: 17
Автор ответа:
 Programmer



Вопросов: 71
Ответов: 246
 Профиль | | #17 Добавлено: 02.09.12 00:42
EROS, я знаком с этой информацией, но все-равно спасибо за интересные подробности :)

Я так понимаю, "оживление" может произойти только в том случае, если объект в финализаторе где-нибудь сохранит ссылку на себя. Но у меня точно такого не происходит. Возможно, это глюк Extension-методов (объект "исчезает", когда я передаю его в качестве аргумента extension-метода).

  1.  
  2. // вызывается
  3. void a(BinaryWriter wr) {
  4.  wr.Write(1); // успешно
  5.  b(123);
  6. }
  7.  
  8. ...
  9. public static void b(this BinaryWriter wr, float data){
  10.    wr.Write(data); // перегруженный метод MemoryStream.Write(byte[],int,int) сообщает, что установлен флаг "вызван финализатор".
  11. }

Ответить

Номер ответа: 18
Автор ответа:
 Programmer



Вопросов: 71
Ответов: 246
 Профиль | | #18 Добавлено: 02.09.12 00:44
поправка
  1.     
  2.     // вызывается
  3.     void a(BinaryWriter wr) {
  4.      wr.Write(1); // успешно
  5.      wr.b(123);
  6.     }
  7.      
  8.     ...
  9.     public static void b(this BinaryWriter wr, float data){
  10.        wr.Write(data); // перегруженный метод MemoryStream.Write(byte[],int,int) сообщает, что установлен флаг "вызван финализатор".
  11.     }
  12.  

Ответить

Номер ответа: 19
Автор ответа:
 Programmer



Вопросов: 71
Ответов: 246
 Профиль | | #19 Добавлено: 02.09.12 00:53
Стоп! А ведь это все объясняет! Extension-метод может быть вызван на null-объекте. GC определяет, что код, который идет после первого Write прекрасно может обойтись без объекта BinaryWriter. Это наиболее вероятная версия. Может быть, это даже можно воспроизвести.

Ответить

Номер ответа: 20
Автор ответа:
 Artyom



Разработчик

Вопросов: 130
Ответов: 6602
 Профиль | | #20 Добавлено: 02.09.12 04:15
весь код своего Stream'а покажи

Ответить

Номер ответа: 21
Автор ответа:
 Artyom



Разработчик

Вопросов: 130
Ответов: 6602
 Профиль | | #21 Добавлено: 02.09.12 04:43
Programmer пишет:
EROS, я знаком с этой информацией, но все-равно спасибо за интересные подробности

Вообще-то это не EROS писал а я

Programmer пишет:
Возможно, это глюк Extension-методов (объект "исчезает", когда я передаю его в качестве аргумента extension-метода).

Programmer, глюк только у тебя в коде. В компиляторе csc этих глюков нет.

Programmer пишет:
Extension-метод может быть вызван на null-объекте. GC определяет, что код, который идет после первого Write прекрасно может обойтись без объекта BinaryWriter. Это наиболее вероятная версия. Может быть, это даже можно воспроизвести.


В твоем коде ты сможешь воспроизвести только NullReferenceException, так как до вызова Extension метода вызывается Instance метод класса BinaryWriter. Instance методы вызываются инструкцией IL callvirt, которая в первую очередь проверяет референс на null, и в случае чего кидает NullReferenceException (даже в том случае, если instance метод не использует ссылку на this).

В любом случае, скорее всего ты движешься не в том направлении, поскольку все локальные переменные хранятся в стеке и в регистрах процессора, а стек и регистры тоже являются корнями, с которыми работает GC. Поэтому ситуация при которой ссылка из локальной переменной вдруг станет указывать на мусор исключена.

Ответить

Номер ответа: 22
Автор ответа:
 жванецкий



Вопросов: 0
Ответов: 8
 Профиль | | #22 Добавлено: 02.09.12 11:44
Programmer, може ж ти об'єкт створений у одному потоці у іншому далі використовуєш. Спробуй його статичним об'явити. Бо не може такого буть. Наведи код, спєци здєшніе тобі швидко все по полицям розкладуть. ps. Сайт так і закинув свій бачу.

Ответить

Номер ответа: 23
Автор ответа:
 Artyom



Разработчик

Вопросов: 130
Ответов: 6602
 Профиль | | #23 Добавлено: 02.09.12 13:30
Пане жванецький, немає ніяких проблем у тому що об'єкт створюється в одному потоці а використовується у іншому. Збирач сміття досить коректно працює у таких ситуаціях, тому що під час збірки сміття усі робочи потоки призупиняються. Більш того з одним об'єктом одночасно можуть працювати кілька різних потоків, але треба мати на увазі, що при цьому почнеться конкуренція між різними ядрами процесору за кеш-лінію, тому швидкодія знизиться, можливо даже у кілька разів. Така сама ситуація можлива, коли різні потоки працюють з різними об'єктами, але такими що були створені майже одночасно, які розміщуються поруч у пам'яті і на одній кеш-лінії

Ответить

Номер ответа: 24
Автор ответа:
 Programmer



Вопросов: 71
Ответов: 246
 Профиль | | #24 Добавлено: 02.09.12 15:14
Artyom, прошу прощения, ночью писал, спасибо тебе.

Я сохраняю ссылки в приватном static-поле, но мне не нравится такое решение - если я забуду вызвать Dispose, стрим останется в памяти.

Выложить 1800 строк кода? А будешь ли ты в нем разбираться? Ладно, постараюсь сократить что возможно.

p.s. жванецкий, сайт продан и более мне не принадлежит. Если новый веб-мастер не справляется, это не имеет ко мне никакого отношения.

Ответить

Номер ответа: 25
Автор ответа:
 Programmer



Вопросов: 71
Ответов: 246
 Профиль | | #25 Добавлено: 02.09.12 21:49
Код стрима:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Threading;
  5.  
  6. namespace FlexibleGameServer
  7. {
  8.     public class SimpleDataBuffer : MemoryStream, IDisposable
  9.     {
  10.         public override void Write(byte[] buffer, int offset, int count)
  11.         {
  12.             bool disposed = _disposeCalled;
  13.             try
  14.             {
  15.                 if (_disposeCalled || _asEmpty) throw new ObjectDisposedException("SimpleDataBuffer");
  16.                 base.Write(buffer, offset, count);
  17.             }
  18.             catch (ObjectDisposedException e) { throw new ObjectDisposedException("SimpleDataBuffer disposed? " + _disposeCalled + ", was " + disposed + ", disposed at " + _disposedAt, e); }
  19.         }
  20.  
  21.         public override void WriteByte(byte value)
  22.         {
  23.             if (_disposeCalled || _asEmpty) throw new ObjectDisposedException("SimpleDataBuffer");
  24.             base.WriteByte(value);
  25.         }
  26.  
  27.         public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
  28.         {
  29.             if (_disposeCalled || _asEmpty) throw new ObjectDisposedException("SimpleDataBuffer");
  30.             return base.BeginRead(buffer, offset, count, callback, state);
  31.         }
  32.  
  33.         public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
  34.         {
  35.             if (_disposeCalled || _asEmpty) throw new ObjectDisposedException("SimpleDataBuffer");
  36.             return base.BeginWrite(buffer, offset, count, callback, state);
  37.         }
  38.  
  39.         public override long Seek(long offset, SeekOrigin loc)
  40.         {
  41.             if (_disposeCalled || _asEmpty) throw new ObjectDisposedException("SimpleDataBuffer");
  42.             return base.Seek(offset, loc);
  43.         }
  44.  
  45.         public override byte[] GetBuffer()
  46.         {
  47.             if (_disposeCalled || _asEmpty) throw new ObjectDisposedException("SimpleDataBuffer");
  48.             return base.GetBuffer();
  49.         }
  50.  
  51.         public override byte[] ToArray()
  52.         {
  53.             if (_disposeCalled || _asEmpty) throw new ObjectDisposedException("SimpleDataBuffer");
  54.             return base.ToArray();
  55.         }
  56.         
  57.         static readonly SimpleDataBuffer[] EmptyBuffer = new SimpleDataBuffer[5000];//, _emptyBuffer2, _emptyBuffer3, _emptyBuffer4, _emptyBuffer5;
  58.         static int _emptyBufferUsed;
  59.         static int _emptyBufferBegin;
  60.         static readonly object EmptyBufferLock = new object();
  61.  
  62.         public static SimpleDataBuffer Create()
  63.         {
  64.             if (_emptyBufferUsed == 0) return new SimpleDataBuffer();
  65.  
  66.             lock (EmptyBufferLock)
  67.             {
  68.                 if (_emptyBufferUsed != 0)
  69.                 {
  70.                     SimpleDataBuffer ret = EmptyBuffer[_emptyBufferBegin];
  71.                     _emptyBufferUsed--;
  72.                     _emptyBufferBegin++;
  73.  
  74.                     Monitor.Enter(ret._dispLock);
  75.                     if (ret._disposeCalled || !ret._asEmpty || !ret.CanRead || ret.Writer._disposed || ret.Reader._disposed)
  76.                     {
  77.                         Monitor.Exit(ret._dispLock);
  78.                         return Create();
  79.                     }
  80.                     try
  81.                     {
  82.                         if (ret.Length != 0) ret.SetLength(0);
  83.                         ret._asEmpty = false;
  84.                         return ret;
  85.                     }
  86.                     catch { }
  87.                     finally
  88.                     {
  89.                         Monitor.Exit(ret._dispLock);
  90.                     }
  91.                     return Create();
  92.                 }
  93.                 return new SimpleDataBuffer();
  94.             }
  95.  
  96.  
  97.         }
  98.  
  99.         static bool SetEmptyBuffer(SimpleDataBuffer buffer)
  100.         {
  101.             if (_emptyBufferUsed == EmptyBuffer.Length) return false;
  102.             //if (_emptyBuffer5 != null) return false;
  103.             lock (EmptyBufferLock)
  104.             {
  105.                 if (_emptyBufferUsed == EmptyBuffer.Length) return false;
  106.  
  107.                 if (_emptyBufferBegin > 0)
  108.                 {
  109.                     _emptyBufferBegin--;
  110.                     EmptyBuffer[_emptyBufferBegin] = buffer;
  111.                 }
  112.                 else EmptyBuffer[_emptyBufferUsed] = buffer;
  113.                 buffer._asEmpty = true;
  114.                 _emptyBufferUsed++;
  115.  
  116.  
  117.                 return true;
  118.             }
  119.  
  120.         }
  121.  
  122.         public BinaryWriter Writer { get; protected set; }
  123.         public BinaryReader Reader { get; protected set; }
  124.  
  125.         static readonly HashSet<SimpleDataBuffer> Objects = new HashSet<SimpleDataBuffer>();
  126.  
  127.         protected SimpleDataBuffer()
  128.         {
  129.             this.Writer = new BinaryWriter(this);
  130.             this.Reader = new BinaryReader(this);
  131.             // ReSharper disable DoNotCallOverridableMethodsInConstructor
  132.             Position = 0;
  133.             // ReSharper restore DoNotCallOverridableMethodsInConstructor
  134.             lock (Objects) Objects.Add(this);
  135.         }
  136.  
  137.  
  138.         public class BinaryWriter : System.IO.BinaryWriter
  139.         {
  140.             public BinaryWriter(Stream output) : base(output)
  141.             {
  142.             }
  143.  
  144.             internal bool _disposed;
  145.  
  146.             protected override void Dispose(bool disposing)
  147.             {
  148.                 _disposed = true;
  149.                 base.Dispose(disposing);
  150.             }
  151.         }
  152.  
  153.         public class BinaryReader : System.IO.BinaryReader
  154.         {
  155.             public BinaryReader(Stream input) : base(input)
  156.             {
  157.             }
  158.  
  159.             internal bool _disposed;
  160.  
  161.             protected override void Dispose(bool disposing)
  162.             {
  163.                 _disposed = true;
  164.                 base.Dispose(disposing);
  165.             }
  166.         }// BinaryReader
  167.  
  168.         public override void Close()
  169.         {
  170.             Dispose(true);
  171.         }
  172.  
  173.         bool _disposeCalled;
  174.         bool _asEmpty;
  175.         readonly object _dispLock = new object();
  176.  
  177.         string _disposedAt;
  178.  
  179.         protected override void Dispose(bool disposing)
  180.         {
  181.             lock (this._dispLock)
  182.             {
  183.                 if (this._disposeCalled || this._asEmpty) return;
  184.                 if (SetEmptyBuffer(this)) return;
  185.                 _disposedAt = Environment.StackTrace;
  186.                 this._disposeCalled = true;
  187.             }
  188.  
  189.             try
  190.             {
  191.                 Reader.Close();
  192.  
  193.  
  194.                 Writer.Close();
  195.                 //Writer = null;
  196.                 base.Close();
  197.                 base.Dispose(disposing);
  198.             }
  199.             finally
  200.             {
  201.                 lock (Objects) Objects.Remove(this);
  202.             }
  203.         }
  204.  
  205.         void IDisposable.Dispose()
  206.         {
  207.             Dispose(true);
  208.         }
  209.  
  210.         ~SimpleDataBuffer()
  211.         {
  212.             _disposedAt = "\n at ~SimpleDataBuffer: \n" + Environment.StackTrace;
  213.             if (_disposeCalled) return;
  214.             _disposeCalled = true;
  215.             try
  216.             {
  217.                 Reader.Close();
  218.                 Writer.Close();
  219.             }
  220.             catch { }
  221.             try
  222.             {
  223.                 base.Close();
  224.             }
  225.             catch { }
  226.             try
  227.             {
  228.                 base.Dispose(true);
  229.             }
  230.             catch { }
  231.         }
  232.     } // SimpleDataBuffer2
  233. }

Ответить

Номер ответа: 26
Автор ответа:
 Artyom



Разработчик

Вопросов: 130
Ответов: 6602
 Профиль | | #26 Добавлено: 02.09.12 23:36
Так и хочется тут сказать over reliability.

Вобщем, налицо какая-то каша у вас в голове.

Путаете Dispose и финализацию.
В коде логика неверная. Смотри, у тебя в финализаторе есть вызов метода Dispose, при этом логика кода построена так, что этот метод не сможет отработать, так как в финализаторе ты уже поставил флаг _disposeCalled, Dispose его считывает и сразу выходит из метода.

Далее, в методе Dispose (bool disposing) аргумент disposing используется для того, чтоб метод мог понять кто инициировал его вызов - или Dispose, или финализатор. Если вызов делается из Dispose, то передается true. Если из финализатора - то False. У вас в обоих случаях передается true

По большому счету здесь нужно удалить абсолютно весь код, который начинается со строчки
  1. public override void Close()

и до конца класса. Он не делает абсолютно никакой полезной работы с точки зрения управления памяти.

Дальше убрать статический список Objects. Это уже настоящая утечка памяти. Причем пока на объект есть ссылка из статического HashSet, он не будет признан мусором. И, сответственно, ваш финализатор для него не вызовется.

По сути единственное, что стоит делать в финализаторе - это освобождать неуправляемые ресурсы, котоыре не были удалены штатно (через прямой вызов Dispose), поскольку, во-первых, кроме вас никто больше не знает как эти ресурсы освобождать, а во-вторых, это последний шанс вообще что-то удалить, после этого неуправляемые ресурсы повиснут и освободить их уже не представится возможности.

У вас в коде не используется ни одного неуправляемого ресурса, следовательно, в финализаторе здесь нет необходимости.

С Dispose сложнее. У вас очень неудачно выбрано отношение между вашим классом SimpleDataBuffer и MemoryStream. Здесь гораздо уместнее бы выглядела композиция а не наследования. Тем не менее вы сделали что сделали. В таком случае я бы оставил метод Dispose, в нем нужно делать только

  1. Writer.Flush();
  2. Base.Dispose(true);


Первое чтоб из буфера Writer контент сбросился в поток. Второе, я думаю, понятно зачем. Но тут нужно понимать что это результат не изменит, даже если и буфер будет сброшен, контент дальше никуда не попадет, так как сам поток тоже закрывается.

Дальше можно убрать все проверки флага _disposeCalled. MemoryStream имеет встроенный механизм, препятствующий чтению данных из него после закрытия потока.

  1.             // ReSharper disable DoNotCallOverridableMethodsInConstructor
  2.             Position = 0;
  3.             // ReSharper restore DoNotCallOverridableMethodsInConstructor


Есть определенная причина, по которой Resharper показывает это сообщения. Она состоит в том, что вызов виртуального метода из конструктора может привести к ситуации, когда Instance метод класса вызывается ДО того как конструктор этого класса был вызван, что в свою очередь может причести к непредсказуемому поведению. Поэтому эту строчку нужно выпилить отсюда, тем более что MemoryStream и так инициализируется на нулевой позиции.

  1.                     Monitor.Enter(ret._dispLock);
  2.                     if (ret._disposeCalled || !ret._asEmpty || !ret.CanRead || ret.Writer._disposed || ret.Reader._disposed)
  3.                     {
  4.                         Monitor.Exit(ret._dispLock);
  5.                         return Create();
  6.                     }

При определенных обстоятельствах (например, асинхронное исключение) этот код залочит Monitor и не освободит его. И следующая попытка вызова этого метода навечно его "повесит". Нужно переписать с try или не выделываться и сделать обычным lock'ом

  1.         static readonly SimpleDataBuffer[] EmptyBuffer = new SimpleDataBuffer[5000];//, _emptyBuffer2, _emptyBuffer3, _emptyBuffer4, _emptyBuffer5;

Это, я так полагаю, пул объектов? Если так, то безжалостно убрать. Пул можно применять в тех случаях, когда речь идет о ресурсах, инициализация которых занимает длительное время (например, как в случае с подключениями к БД). Этого нельзя сказать о MemoryStream - выделение памяти в куче выполняется очень быстро.

Ответить

Номер ответа: 27
Автор ответа:
 Artyom



Разработчик

Вопросов: 130
Ответов: 6602
 Профиль | | #27 Добавлено: 02.09.12 23:46
Я в свое время писал игровой сервер, позволю привести несколько фактов.

Windows-служба.
Если не ошибаюсь, фактически запускается 3 или 4 потока.
В службе поднимается TCP-сервер.
Кол-во одновременных активных подключений не ограничено со стороны сервера (если не ошибаюсь, на тестах удавалось подключить 64+ тысячи клиентов, дальше кончились клиентские локальные порты, сервер же был готов и дальше принимать подключения)
Самый большой аптайм, который я фиксировал, составлял около 2 месяцев, больше не было из-за того что сервак постоянно перегружается ставя Windows Update.
В коде 15К+ строк кода и ни одного финализатора.
Когда нагрузка с сервера уходит, потребление памяти падает до 100 с лишним метров, из чего можно сделать определенные выводы о том, был ли предыдущий пункт правильным решением.

Ответить

Номер ответа: 28
Автор ответа:
 EROS



Вопросов: 58
Ответов: 4255
 Профиль | | #28 Добавлено: 02.09.12 23:52
Artyom пишет:
 и ни одного финализатора.

+1..
финализатор вообще достаточно редкое явление в коде с#..
обычно всегда хвататет грамотно реализовать IDisposable и освободить там неуправляемые ресурсы

Ответить

Номер ответа: 29
Автор ответа:
 бзззззьььь



Вопросов: 0
Ответов: 6
 Профиль | | #29 Добавлено: 02.09.12 23:56
Я сохраняю ссылки в приватном static-поле, но мне не нравится такое решение - если я забуду вызвать Dispose, стрим останется в памяти.


Ну, юзать MemoryStream не обов'язково; таку штуку можна й за допомогою List<byte> реалізувати без особливих додаткових зусиль. Якшо максимальна швидкодія не відіграє вирішальну роль у твоєму задумі. Можна в разі потреби реалізувати просто похідним від Stream, коли треба. Також й без BinaryWriter обійтись, змінивши на BitConverter. Було б надійно, просто, дешево і сердито як кажеться. Дуже заплутаний в тебе код вийшов. В мене час від часу такі проблеми виникають при роботі з багатопоточним кодом.

Ответить

Номер ответа: 30
Автор ответа:
 Programmer



Вопросов: 71
Ответов: 246
 Профиль | | #30 Добавлено: 02.09.12 23:57
Смотри, у тебя в финализаторе есть вызов метода Dispose, при этом логика кода построена так, что этот метод не сможет отработать, так как в финализаторе ты уже поставил флаг _disposeCalled, Dispose его считывает и сразу выходит из метода.

Я вызываю base Dispose. Там мой флаг не проверяется.


Далее, в методе Dispose (bool disposing) аргумент disposing используется для того, чтоб метод мог понять кто инициировал его вызов - или Dispose, или финализатор. Если вызов делается из Dispose, то передается true. Если из финализатора - то False. У вас в обоих случаях передается true

Да, для меня это приемлемо.

Дальше убрать статический список Objects. Это уже настоящая утечка памяти. Причем пока на объект есть ссылка из статического HashSet, он не будет признан мусором. И, сответственно, ваш финализатор для него не вызовется.

Я как раз об этом поле и говорил в предыдущем посте. Без него объект финализируется когда на него есть живая ссылка.

По сути единственное, что стоит делать в финализаторе - это освобождать неуправляемые ресурсы, котоыре не были удалены штатно (через прямой вызов Dispose), поскольку, во-первых, кроме вас никто больше не знает как эти ресурсы освобождать, а во-вторых, это последний шанс вообще что-то удалить, после этого неуправляемые ресурсы повиснут и освободить их уже не представится возможности.
В моем случае финализатор нужен для того, чтобы я мог не вызывать Dispose. Т.е. Dispose нужен, если требуется освободить ресурсы прямо сейчас. Мой финализатор освобождает эти ресурсы, если они не были освобождены из Dispose. Я не знаю, освободит ли MemoryStream все свои ресурсы в финализаторе или нет (если не вызывать Dispose), поэтому предпочитаю вручную вызвать base.Dispose в финализаторе.


Дальше можно убрать все проверки флага _disposeCalled. MemoryStream имеет встроенный механизм, препятствующий чтению данных из него после закрытия потока.
Ты вообще читал код? У меня механизм кэширования, который помещает стрим в пул при вызове Dispose, при этом устанавливается флаг asEmpty. А если пул заполнен - только тогда делается настоящий Dispose. Настоящий смысл проверок не в _disposeCalled, а в _asEmpty. Create() не создает новый стрим, а берет его из пула, если это возможно.

Есть определенная причина, по которой Resharper показывает это сообщения. Она состоит в том, что вызов виртуального метода из конструктора может привести к ситуации, когда Instance метод класса вызывается ДО того как конструктор этого класса был вызван, что в свою очередь может причести к непредсказуемому поведению.
Спасибо, я умею читать. Тем не менее, такое поведение бывает необходимо. Хотя не спорю, в данном случае это лишнее.

С Dispose сложнее. У вас очень неудачно выбрано отношение между вашим классом SimpleDataBuffer и MemoryStream. Здесь гораздо уместнее бы выглядела композиция а не наследования. Тем не менее вы сделали что сделали.
В том виде, в котором я выложил, этот код вообще никому не может быть полезен. Но в полном коде выбранный паттерн себя оправдывает.

При определенных обстоятельствах (например, асинхронное исключение) этот код залочит Monitor и не освободит его. И следующая попытка вызова этого метода навечно его "повесит". Нужно переписать с try или не выделываться и сделать обычным lock'ом
Откуда здесь взятся асинхронному исключению? Или срабатывает эта ветка или далее выполнение как раз и ведет в try-finally.

Это, я так полагаю, пул объектов? Если так, то безжалостно убрать. Пул можно применять в тех случаях, когда речь идет о ресурсах, инициализация которых занимает длительное время (например, как в случае с подключениями к БД). Этого нельзя сказать о MemoryStream - выделение памяти в куче выполняется очень быстро.
Профайлер показывает значительный выигрышь. Не было бы тормозов - не было бы и пула.

Ответить

Страница: 1 | 2 | 3 |

Поиск по форуму



© Copyright 2002-2011 VBNet.RU | Пишите нам