以下是我在iOS xamarin应用程序中使用的一些数据访问代码的简化表示:
public async Task<List<Foo>> GetAllFoos()
{
var foos = new List<Foo>();
using(CommandWrapper command = GetCommandWrapper("SELECT Id, Name, Rank FROM Foos"))//this creates SqliteCommand object and puts it in wrapper object
{
using(ReaderWrapper reader = ExecuteReaderAsync(_Connection, command))
{
while(reader.Read())
{
var foo = ConstructFoo(reader);//construct foo with column values read from reader
foos.Add(foo);
}
}
}
return foos;
}
private SemaphoreSlim _commandLockSemaphore = new SemaphoreSlim(1, 1);//only 1 thread at once
private async ExecuteReaderAsync(Mono.Data.Sqlite.SqliteConnection connection, CommandWrapper command)
{
if(!(await _SemaphoreLock.WaitAsync(_TimeoutTimespan))) throw new Exception("timeout");
//lock now held
Mono.Data.Sqlite.SqliteCommand sqliteCommand = command.UnderlyingCommand;
try
{
sqliteCommand.Connection = connection;
IDataReader rawReader = await sqliteCommand.ExecuteReaderAsync();
}
catch(Exception)
{
//emergency lock release
_SemaphoreLock.Release();
throw;
}
return new ReaderWrapper(){DataReader = rawReader, SemaphoreLock = _SemaphoreLock};
}
internal class ReaderWrapper : IDisposable
{
internal IDataReader DataReader{get; set;}
internal SemaphoreSlim SemaphoreLock{get; set;}
//... [read methods]
public void Dispose()
{
DataReader.Dispose();
SemaphoreLock.Release();
}
}
由于我们知道Sqlite不支持连接上的多个线程,因此我们将SemaphoreSlim放在适当的位置,以确保连接的使用和任何命令的执行仅发生在单个线程中。 SemaphoreSlim随后在阅读器疲惫不堪后被释放。
但是,当我有多个线程调用GetAllFoos
时,应用程序崩溃时会出现以下情况:
2015-08-19 14:33:22.296 MyCoolIosApp[8421:5311923] critical: Stacktrace: 2015-08-19 14:33:22.296 MyCoolIosApp[8421:5311923] critical: at 2015-08-19 14:33:22.297 MyCoolIosApp[8421:5311923] critical: at (wrapper managed-to-native) Mono.Data.Sqlite.UnsafeNativeMethods.sqlite3_prepare (intptr,intptr,int,intptr&,intptr&) 2015-08-19 14:33:22.297 MyCoolIosApp[8421:5311923] critical: at Mono.Data.Sqlite.SQLite3.Prepare (Mono.Data.Sqlite.SqliteConnection,string,Mono.Data.Sqlite.SqliteStatement,uint,string&) [0x00044] in //Library/Frameworks/Xamarin.iOS.framework/Versions/8.10.4.46/src/mono/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLite3.cs:268 2015-08-19 14:33:22.298 MyCoolIosApp[8421:5311923] critical: at Mono.Data.Sqlite.SqliteCommand.BuildNextCommand () [0x00019] in //Library/Frameworks/Xamarin.iOS.framework/Versions/8.10.4.46/src/mono/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteCommand.cs:230 2015-08-19 14:33:22.298 MyCoolIosApp[8421:5311923] critical: at Mono.Data.Sqlite.SqliteCommand.GetStatement (int) [0x0000b ] in //Library/Frameworks/Xamarin.iOS.framework/Versions/8.10.4.46/src/mono/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteCommand.cs:264 2015-08-19 14:33:22.299 MyCoolIosApp[8421:5311923] critical: at Mono.Data.Sqlite.SqliteDataReader.NextResult () [0x000cc] in //Library/Frameworks/Xamarin.iOS.framework/Versions/8.10.4.46/src/mono/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteDataReader.cs:914 2015-08-19 14:33:22.299 MyCoolIosApp[8421:5311923] critical: at Mono.Data.Sqlite.SqliteDataReader..ctor (Mono.Data.Sqlite.SqliteCommand,System.Data.CommandBehavior) [0x00051] in //Library/Frameworks/Xamarin.iOS.framework/Versions/8.10.4.46/src/mono/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteDataReader.cs:89 2015-08-19 14:33:22.300 MyCoolIosApp[8421:5311923] critical: at Mono.Data.Sqlite.SqliteCommand.ExecuteReader (System.Data.CommandBehavior) [0x00006] in //Library/Frameworks/Xamarin.iOS.framework/Versions/8.10.4.46/src/mono/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteCommand.cs:539 2015-08-19 14:33:22.300 MyCoolIosApp[8421:5311923] critical: at Mono.Data.Sqlite.SqliteCommand.ExecuteReader () [0x00000] in //Library/Frameworks/Xamarin.iOS.framework/Versions/8.10.4.46/src/mono/mcs/class/Mono.Data.Sqlite/Mono.Data.Sqlite_2.0/SQLiteCommand.cs:551 ...
根据各种讨论,该错误是由iOS设备上的Sqlite上的线程太多引起的。我可以证实这一点,因为如果我有一对线程尝试在while循环中无休止地调用GetAllFoos
,它会100%崩溃。当我重新安排锁定时(例如,在构造和处理命令时添加一个简单的锁定语句),它解决了问题:
public async Task<List<Foo>> GetAllFoos()
{
var foos = new List<Foo>();
lock(_someStaticObject)//why is this needed
{
using(CommandWrapper command = GetCommandWrapper("SELECT Id, Name, Rank FROM Foos"))//this creates SqliteCommand object and puts it in wrapper object
{
using(ReaderWrapper reader = ExecuteReaderAsync(_Connection, command))
{
while(reader.Read())
{
var foo = ConstructFoo(reader);//construct foo with column values read from reader
foos.Add(foo);
}
}
}
}
return foos;
}
就我所知,SqliteCommand上的Dispose方法(可能还有构造函数)导致了并发问题。
在Mono中SqliteCommand对象的构造函数和Dispose方法是否为线程安全?或者我是否需要考虑其中一个或两个是锁定目的的关键部分?
答案 0 :(得分:1)
使用Mono的好处在于它是开源的,所以让我们回答你的问题“Mono中SqliteCommand对象线程安全的构造函数和Dispose方法吗?”
如果我们快速浏览一下:
在这里:
...然后我们可以强调回答“不,构造函数和Mono的SQLiteCommand的Dispose都不是线程安全的”。
就此而言,假设任何地方都没有线程安全是正确的,除非它通过文档明确说明。即便如此,它有时也是谎言。
(现在微软的东西也是开源的,所以我们可以用同样的方式在Mono之外回答这些问题。是的!)