DB-соединение в отдельном потоке - как лучше?

Я создаю приложение, которое обращается к базе данных. При каждом доступе к базе данных приложение ожидает завершения задания. Чтобы пользовательский интерфейс был отзывчивым, я хочу поместить все элементы базы данных в отдельный поток.
Вот моя идея:

  • DB-поток создает все компоненты базы данных, в которых он нуждается при создании
  • Теперь поток просто сидит и ждет команды
  • Если он получает команду, он выполняет действие и возвращается в режим ожидания. В течение этого времени основной поток ждет.
  • DB-поток живет до тех пор, пока приложение работает

Это звучит нормально?
Каков наилучший способ получить результаты базы данных из db-потока в основной поток?
Я пока мало что сделал с потоками, поэтому мне интересно, может ли db-thread создать компонент запроса, из которого основной поток считывает результаты. Основной поток и поток БД никогда не получат доступ к запросу одновременно. Это все еще вызовет проблемы?

4 ответа

Решение

Прежде всего - если у вас мало опыта работы с многопоточностью, не начинайте с классов VCL. Используйте OmniThreadLibrary по следующим причинам:

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

DB-поток создает все компоненты базы данных, в которых он нуждается при создании

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

Если он получает команду, он выполняет действие и возвращается в режим ожидания. В течение этого времени основной поток ждет.

Первая часть обрабатывается нормально, когда используется OTL. Однако - не ждите основного потока, это принесет небольшое преимущество по сравнению с выполнением доступа к базе данных непосредственно в потоке VCL. Вам нужен асинхронный дизайн, чтобы наилучшим образом использовать несколько потоков. Рассмотрим стандартную форму браузера базы данных, которая имеет элементы управления для фильтрации записей. Я справляюсь с этим путем (повторного) запуска таймера каждый раз, когда меняется один из элементов управления. Как только пользователь заканчивает редактирование, запускается событие таймера (скажем, через 500 мс), и запускается задача, которая выполняет инструкцию, которая выбирает данные в соответствии с критериями фильтрации. Содержимое сетки очищается и заполняется только после завершения задачи. Однако это может занять некоторое время, поэтому поток VCL не ожидает завершения задачи. Вместо этого пользователь может даже изменить критерии фильтра снова, и в этом случае текущая задача отменяется и запускается новая. OTL дает вам событие для завершения задачи, поэтому асинхронный дизайн легко реализовать.

Каков наилучший способ получить результаты базы данных из db-потока в основной поток?

Обычно я не использую компоненты с поддержкой данных для многопоточных приложений БД, но использую стандартные элементы управления, которые являются представлениями для бизнес-объектов. В задачах базы данных я создаю эти объекты, помещаю их в списки, и событие завершения задачи передает список в поток VCL.

Основной поток и поток БД никогда не получат доступ к запросу одновременно.

Со всеми компонентами, которые загружают данные по требованию, вы не можете быть в этом уверены. Часто только первые записи извлекаются из базы данных, и извлечение продолжается после того, как они были использованы. Такие компоненты, очевидно, не должны совместно использоваться потоками.

То, что вы ищете, - это стандартная техника доступа к данным, называемая асинхронным выполнением запроса. Некоторые компоненты доступа к данным реализуют эту функцию простым в использовании способом. По крайней мере, dbGo (ADO) и AnyDAC реализуют это. Давайте рассмотрим dbGo.

Идея проста - вы вызываете удобные методы набора данных, такие как Open. Метод запускает требуемую задачу в фоновом потоке и сразу же возвращается. Когда задача будет выполнена, будет запущено соответствующее событие, уведомляющее приложение о том, что задача выполнена.

Стандартный подход с приложениями GUI БД и методом Open заключается в следующем (черновик):

  • включить eoAsyncExecute, eoAsyncFetch, eoAsyncFetchNonBlock в набор данных ExecuteOptions;
  • отключить TDataSource.DataSet от набора данных;
  • установить набор данных OnFetchComplete в proc P;
  • показать диалог "Привет! Мы выполняем тяжелую работу по обработке ваших запросов. Пожалуйста, подождите...";
  • вызвать метод Open набора данных;
  • когда выполнение запроса будет завершено, будет вызван OnFetchComplete, поэтому P. и P скрывают диалоговое окно "Ожидание" и подключают TDataSource.DataSet обратно к набору данных.

Также в вашем диалоговом окне "Ожидание" может быть кнопка "Отмена", которую пользователь может использовать для отмены слишком длинного запущенного запроса.

Я реализовал обе стратегии: создание пула потоков и создание специальных потоков.

Я предлагаю начать с создания adhoc-потока, его проще реализовать и масштабировать.

Переходите в пул потоков только в том случае, если (с тщательной оценкой) (1) много ресурсов (и времени) вложено в создание потока и (2) у вас много запросов на создание.

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

Обратитесь к документации по классам, компонентам и функциям, используемым потоком, чтобы убедиться, что они являются потокобезопасными, то есть их можно использовать одновременно из разных потоков. Если нет, вам нужно будет синхронизировать доступ. В некоторых случаях вы можете найти небольшие различия в безопасности нитей. В качестве примера см. DateTimeToStr.

Если вы создаете свой поток при запуске и повторно используете его позже, когда вам это нужно, вы должны обязательно отключать компоненты db (grid..) от базового источника данных (disableControls) каждый раз, когда вы "обрабатываете" данные.

Для простоты я бы унаследовал TThread и реализовал всю бизнес-логику в своем собственном классе. Результирующий набор данных будет членом этого класса, и я подключу его к db-осведомленным композитам с помощью synchronize.

В любом случае, также очень важно делегировать как можно больше работы серверу БД и поддерживать максимально упрощенный пользовательский интерфейс. Firebird - мой любимый db-сервер: триггеры для избранных, пользовательские dll-файлы UDF, разработанные в Delphi, много поточно-ориентированных db-компонентов с множеством примеров и хорошей поддержкой (форум): jvUIB...

Удачи

Другие вопросы по тегам