РЕДАКТИРОВАТЬ: (решено числом 6)
Что вызывает сбой System.AccessViolationException
или System.ExecutionEngineException
в SQLitePCLRaw.provider.e_sqlite3.dll, когда несколько потоков одновременно получают доступ к моему FooDbContext
?
У меня есть приложение Xamarin Forms (3.5.0.169047), поддерживающее UWP, Android и iOS, использующее NETstandard 2.0.3, Microsoft.EntityFrameworkCore 2.2.4, Microsoft.EntityFrameworkCore.Sqlite 2.2.4, и у меня воспроизводимая аварийная ситуация(это наверняка случается в UWP), что у меня проблемы с разрешением, возникающие при одновременном доступе к базе данных sqlite на устройстве из двух разных потоков одновременно.У меня есть процесс синхронизации (передает локальные данные в API и извлекает онлайн-данные из API), который может занять около минуты или около того, поэтому мне нужно выполнить в отдельном потоке, чтобы пользовательский интерфейс реагировал во время его работы.Мне также нужно разрешить запросы локальных данных во время синхронизации, чтобы разрешить навигацию во время синхронизации или другие операции с данными только для чтения в приложении во время синхронизации.Длительная синхронизация работает нормально, если я не выполняю никаких операций доступа к данным во время синхронизации, но происходит сбой сразу после завершения любой прерывающей более короткой операции доступа к данным.
Два исключения сбоя, которые я виделПроисходят (возможно, связанные с синхронизацией, для которых происходит сбой в идентичных репродукциях), как видно из вывода отладки из Visual Studio 2017 (v15.9.5):
Необработанное исключениеТип «System.AccessViolationException» произошел в SQLitePCLRaw.provider.e_sqlite3.dll Попытка чтения или записи в защищенную память.Это часто указывает на то, что другая память повреждена.
Необработанное исключение типа 'System.ExecutionEngineException' произошло в SQLitePCLRaw.provider.e_sqlite3.dll
Во время отладки нет никаких дополнительных подробностей об исключении;это происходит во время разных длинных строк кода синхронизации, в зависимости от времени выполнения прерывающего действия;и долгосрочный код, в котором отладчик показывает возникновение исключения, содержится в блоке Try { ... } Catch(Exception) { ... }
try в моем коде.
Что может быть причиной этого и как я могу его устранить?
Я прошел через все следующее:
Совместимо ли использование SQLite с несколькими потоками?
- Да;по умолчанию он находится в «Сериализованном» режиме согласно https://www.sqlite.org/threadsafe.html и поддерживает многопоточное использование без ограничений.Используемая базовая версия SQLite - 3.26.0, которую я определил при исследовании
Microsoft.Data.Sqlite.SQLiteConnection
информации при отладке.
Это потому, что у меня не может быть большеодно соединение открыто с возможностью записи одновременно?
- Нет;Я даже изменил свои строки подключения, используя
Microsoft.Data.Sqlite.SqliteConnectionStringBuilder
, чтобы получить соответствующие Mode
(SqliteOpenMode.ReadOnly
или SqliteOpenMode.ReadWriteCreate
) в зависимости от потребностей каждого доступа к данным.
Это потому, что FooDbContext
прерывающего потока Dispose()
избавляется от ресурсов, на которые опирался долгосрочный поток FooDbContext
?
- Нет;Я много исследовал это, так как это была последняя точка останова, которую я мог достичь до аварии.Даже когда я переопределил метод
Dispose
в FooDbContext
, чтобы ничего не делать, даже не вызывать Dispose
базового класса (не рекомендуется, но я пробовал это временно), сбой все равно происходил.
Есть ли настройка, которую я могу установить с помощью функции Microsoft.Data.Sqlite.SqliteConnectionStringBuilder
или Microsoft.Data.Sqlite.SQLiteConnection
или Microsoft.EntityFrameworkCore.DbContextOptionsBuilder
UseSqlite
, чтобы убедиться, что используется сериализованный режим (я не был уверен на 100%, чтоэто точка)?
- Нет;Я много смотрел, и эта опция должна быть скрыта во внутренностях выбранной библиотеки SQLite.
Я много читал и исследовал, что все еще давало мне малоиначе я мог бы попробовать.
РЕДАКТИРОВАТЬ: Ответ, который решил эту проблему для меня:
- Я заметил, что dll, из которой пришло исключение, было SQLitePCLRaw.provider.e_sqlite3.dll. Это привело меня к рассмотрению того, что на самом деле устанавливает низкоуровневую библиотеку SQLite, и в конечном итоге именно вызов
SQLitePCL.Batteries_V2.Init();
выбирает платформенно-зависимого низкоуровневого поставщика SQLite для использования. Может ли это быть неправильно?
- Да; получается, что после прочтения информации вики в https://github.com/ericsink/SQLitePCL.raw/wiki/SQLitePCL.Batteries.Init#what-does-batteries_v2init-do, что вызов
SQLitePCL.Batteries_V2.Init
должен был быть выполнен только один раз для каждой платформы (либо в коде, специфичном для платформы, либо в общем коде, если * 1139) * устанавливается в общем проекте, а также в каждом конкретном проекте платформы). Мое использование SQLitePCL.Batteries.Init
было неправильно внутри OnConfiguring
в FooDbContext
, что делало его вызываемым один раз при настройке FooDbContext
, а не только один раз при запуске приложения. Перемещение строки SQLitePCL.Batteries_V2.Init();
из OnConfiguring
в конструктор App.xaml.cs
в моем общем проекте исправило это! Сбои больше не происходили после прерывания доступа к потоку данных. Я действительно надеюсь, что это спасет кого-то от огромных хлопот, которые он спас, пытаясь докопаться до сути.