| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > .NET для новичков > Утечка дескрипторов |
| Автор: N1ko 25.5.2010, 18:32 |
| Здравствуйте. Есть программа, которая сканируют 7 Гб dbf файлов и вставляет инфу из них в другую СУБД. Сканирует их до тех пор, пока не вылетает исключение "Недостаточно системных ресурсов". Запустив програму заново и при этом запустив диспетчер задач,увидел что количество дескрипторов непрерывно растёт. Когда доходит почти до 30000 собственно и вылетает исключение. Нашёл в интренете инфу, что такая проблема возникает в связи с использованием многопоточности.(Реализую её через стандартную компоненту BackGrounWorker) А вот как её решить не совсем понятно. Помогите плз кто чем может. |
| Автор: jonie 25.5.2010, 20:09 |
| Вам нужно пройтись по всему коду в поисках объектов, которые вы создаете, классы которых наследуются от IDisposable [http://msdn.microsoft.com/en-us/library/system.idisposable.aspx]. Далее очень внимательно погуглить по этому интерфейсу и почитать MSDN. Думаю ресурсы как раз и текут и вас в силу недетерменированной природы деструкторов (финализаторов) в .NET - среда просто не успевает разрушать объекты, содержащие ссылки на неуправляемые ресурсы (дескрипторы), и винда глушит ваше приложение.... возможно также, что ошибка в библиотеках которые вы используете подобная имеется. |
| Автор: N1ko 25.5.2010, 23:02 |
| То есть если я правильно понял нужно реализовать IDisposable у всех самодельных классов? |
| Автор: jonie 25.5.2010, 23:07 |
| N1ko, нет неправильно. IDisposable нужен если вам нужно относительно детерменированное (читай "по запросу вами") очистка ресурсов например. Вам надо пройтись по коду и найти места где вы используете классы, наследуемые от IDisposable ... например OleDBConnection наследуется...а дальше using-и использовать или try..finally В общем тут почитайте http://www.rsdn.ru/article/dotnet/GCnet.xml |
| Автор: N1ko 26.5.2010, 12:27 | ||
| Пробовал как вы сказали. Прочитал кучу статей. Уже по 5 раз прошёлся по всему коду. Сделал Dispose у всего что только можно. Ничего не получается (( Вот выложил код, котрый непосредственно отвечает за считывание данных из одного DBF файла и вставляет их в SQLite. Програма в принципе работает без утечки в случае если я не закрываю OleDbReader а в final прописываю GC.Collect(); GC.WaitForPendingFinalizers(); Но где то на 600-700 мб просканеных дбф файлов програма начинает работать очень медленно. Судя по всему из-за сборщика мусора. Возможно глядя на код Вы найдёте ошибку. Буду очень благодарен за Вашу помощ.
|
| Автор: NightmareZ 26.5.2010, 13:31 |
Я считаю, что этого делать не нужно. Нужно лишь вызывать Dispose. Ты уверен, что ошибка тут? Не пробывал отслеживать, где дескрипторы создаются и где уничтожаются? |
| Автор: N1ko 27.5.2010, 10:41 |
| А может ли быть утечка дескрипторов из-за того что я сначала создаю SQLiteCommand, создаю в нём параметры. Но не устанавливаю в них Value. А уже только потом меняю эти значения в цикле много раз. Для этого SQLiteCommand не делаю Dispose() И ещё мне вообще не понятно почему когда я убираю _oleDBReader.Close(); _oleDBReader.Dispose(); но оставляю GarbageCollector всё работает нормально без утечек. |
| Автор: N1ko 27.5.2010, 11:19 | ||
Для сканирования действительно использую DataReader. Но к сожалению не совсем понял, что Вы имели ввиду под запросом формирования.
А как же я в блок finally вставлю _oleDBReader.Close(), если у него область видимости очень маленькая. Ведь Oledbreader я создаю в блоке try. |
| Автор: mrbrooks 27.5.2010, 11:28 | ||||
Имел ввиду, как заполняете исходный DataReader, который обращается *.dbf. если я правильно понимаю, то так:
|
| Автор: N1ko 27.5.2010, 11:38 |
| Да. DbfFile - экземпляр класа, который хранит информацию о местонахождении DFB файла, его размере и тп. Метод ReadData() вытаскивает из него всю инфу. В finally вряд ли получится закрывать Reader, потому что область видимости не позволяет.(Если оюъявить oledbreader перед блоком try) тогда в finally пишет ошибку "unassigned local variable" |
| Автор: mrbrooks 27.5.2010, 11:45 | ||||
ну это не проблема - можно и так:
не нужен ни Close() ни finnaly вот это и интересно. думаю не 7Gb? |
| Автор: N1ko 27.5.2010, 12:04 |
| Нет файлов очень много, каждый из которых от 20 до 400 кб. В общем суть програмы следующая . Пользователь указывают папку, которую нужно просканировать на наличие DBF файлов, вытащить из них инфу и вставить в СУБД. Делаю я это посредством рекурсии. Соответственно DbfFile.ReadData() работает не с 7 Гб одновременно а работает с маленькими объёмами А можно ли как то очистить ресурсы выделенные под String после их затирания? Как я уже писал всё время использую один и то же SQLiteCommand, затирая Value для всех параметров и ставя на их место новые. |
| Автор: mrbrooks 27.5.2010, 12:27 | ||||
string.Empty?
В целом ясно. Идея в принципе работоспособная (настораживает только рекурсия - net обладает отличной библиотекой по работе с каталогами). Имхо проблема, скорее всего просто в проектировании. По данному примеру тяжело судить о том что творится в целом. Подозреваю, что работа с базами у вас свалена вся в один класс, a стоило бы разделить работу с сущностями по отдельным, etc |
| Автор: N1ko 27.5.2010, 12:33 |
| Нет Я уже на этом прокололся недавно. И всё разграничил по сущностям. Для базы создал классы реализующие обёртку для самой БД, таблиц, полей, операций вставки, селекта и тп. В этом смысле вроде бы подход уже нормальный. Хотя изначально всё действительно было свалено в одну кучу. |
| Автор: N1ko 27.5.2010, 16:32 |
| А можно ли как то более точно отследить на каком этапе у меня создаются дескрипторы, нежели запускать програму и смотреть на диспетчер? |
| Автор: mihryak 27.5.2010, 19:24 |
| N1ko, http://memprofiler.com/ |
| Автор: N1ko 28.5.2010, 10:35 |
| Вот за это Вам огромное спасибо. Действительно помогло. Утечка идёт от String как я и предполагал. Насколько я понимаю string - ом должен занимаеться сборщик мусора автоматически. Но судя по всему он этого делать не успевает. Можно ли как то вучную это делать? String.Empty не помагает поскольку судя по всему из памяти строка не удаляется. |
| Автор: uranpro 28.5.2010, 13:52 |
| вот бы код рекурсии увидеть)... Добавлено через 2 минуты и 28 секунд Конец жизни переменной: - до конца области видимости в Debug - до последнего упоминания ее в коде в Release http://forum.vingrad.ru/forum/topic-301328.html © |
| Автор: N1ko 31.5.2010, 10:03 | ||
uranpro ,вот собственно рекурсия. Метод ScanDbfFiles сканирует заданую папку на наличие в ней ДБФ файлов. Если таковые там есть, откурывается ДБФ соединение и с читывается вся инфа с файлов. Второй метод ScanFolder сканирует папку на наличие в ней других папок.
Вот такая вот реализация рекурсии... |
| Автор: mrbrooks 31.5.2010, 11:08 | ||
| N1ko, используйте перегруженный метод DirectoryInfo.GetFiles(String, SearchOption), где SearchOption есть перечисление, характеризующее, искать ли файл в текущем каталоге, или еще и во вложенных. Типа этого:
и никакой рекурсии. |
| Автор: N1ko 31.5.2010, 11:34 |
| Если так делать, возникают дополнительные сложности. Ну допустим просканим мы таким способом заданную папку, соответственно ссылки на все файлы будут находиться в одном хранилище и их будет очень много. Для того что бы прочитать ДБФ файл нужно открыть соединение с папкой, в которой он находится. А допустим таких фалов 20 и они распологаются не подряд. Тогда нам прийдётся создавать и открывать несколько раз одно и то же самое соединение. При преходе к каждому новому файлу прийдётся сверять его месторасположение по сравнению с предыдущим и тп. Считаю что проще будет всё таки рекурсией всё сделать. И в основе перегруженного метода с двумя параметраме наверняка используюется всё та же рекурсия. Хотя в этом и не особо уверен. |
| Автор: PashaPash 1.6.2010, 07:30 | ||
N1ko, так сгруппируй результаты поиска по Path.GetDirectory
|
| Автор: PashaPash 1.6.2010, 09:53 |
| N1ko, кстати, а где в первом примере ты открываешь и закрываешь соединение? |
| Автор: N1ko 1.6.2010, 12:39 | ||||
Во втором примере как раз видно что открываю я соединенеие перед использованием функции InsertFromDbf
и сооответственно закрываю его после её завершения.
В самой функции этого делать не требуется, поскольку для каждого дбф нету смысла открывать и закрывать одно и то же соединение. |