Я не думаю, что SQlite поддерживает истинные параллельные транзакции записи ... Я использовал «неисключительные» транзакции на Android, и они документированы (я пишу своими словами из памяти) как разрешающие параллельные чтенияв то время как может происходить запись.
Теперь ближе к делу ... Рассмотрим документы транзакций SQLite:
https://sqlite.org/lang_transaction.html
Таким образом, с помощьюотложенная транзакция, сама инструкция BEGIN ничего не делает с файловой системой.Блокировки не получаются до первой операции чтения или записи.Первая операция чтения для базы данных создает блокировку SHARED, а первая операция записи создает блокировку RESERVED .Поскольку получение блокировок откладывается до тех пор, пока они не потребуются, возможно, что другой поток или процесс может создать отдельную транзакцию и выполнить запись в базу данных после выполнения BEGIN в текущем потоке.Если транзакция немедленная, то RESERVED блокировки получаются во всех базах данных , как только выполняется команда BEGIN, без ожидания использования базы данных.
(выделение -мой)
ОК, теперь мы узнали, что для записи в базу данных требуется зарезервированная блокировка.
Давайте посмотрим, что это здесь:
https://sqlite.org/lockingv3.html#reserved_lock
Блокировка RESERVED означает, что процесс планирует запись в файл базы данных в какой-то момент в будущем, но в данный момент он просто читает из файла. Только одна зарезервированная блокировка может быть активной одновременно, хотя несколько блокировок SHARED могут сосуществовать с одной зарезервированной блокировкой.RESERVED отличается от PENDING тем, что новые блокировки SHARED могут быть получены при наличии блокировки RESERVED.
OK, поэтому это подтверждает, что SQLite требует блокировки RESERVED для записи в базу данных, а также сообщает нам, чтоодновременно может существовать только одна зарезервированная блокировка -> только одной транзакции разрешено иметь доступ на запись, другие будут ждать.
Теперь, если вы пытаетесь чередовать записи транзакций из двух потоков, каждый поток выполняетсянесколько (гранулярных) транзакций - тогда вот идея:
- Замена Thread.Sleep с Thread.Yield
https://docs.microsoft.com/en-us/dotnet/api/system.threading.thread.yield
Это может помочьс вопросом «Сон текущего записывающего потока не вызвал переключение контекста на нужный нам поток».
Даже с выходом все еще нет никаких гарантий, что ОС / среда выполнения переключится на нужный вам поток, но ... возможно, стоит попробовать, и, по крайней мере, вы не будете искусственно замедлять работу вашего кода.
- Учитывая то, что мы знаем оSQLite «разрешена запись только одной транзакции», я хотел бы рассмотреть следующий шаблон:
1 - создать новый поток, задачей которого является обработка записей в базу данных
2 - очередьоперации записи в этот поток из ваших текущих двух потоков
3 - «Операции» должны быть автономными / достаточными объектами, содержащими все данные, которые они намереваются записать
4 - Наконец,используйте обратный вызов с защелкой (в C # это CountDownEvent, я считаю), чтобы узнать, когда операция завершена, поэтому ваши текущие потоки могут ожидать завершения
Тогда у вас будет только один поток записи (поскольку SQliteи все еще имеют параллелизм между вашими текущими потоками.
Псевдокод:
// Write thread
while (item = blockingQueue.getNextItemToWrite()) {
item.executeWrite(database)
item.signalCompletion()
}
// Thread 1
item = new WriteItem(some data that needs to be written)
WriteThread.enqeue(item)
item.awaitCompletion()
// Thread 2 - same as Thread 1
Где базовый класс WriteItem имеет значение CountDownEvent, равное 1), ожидаемое awaitCompletion, и 2) сигнализируемоеsignalCompletion.
Я уверен, что есть способ обернуть это в более элегантные вспомогательные классы и, возможно, использовать async / await.