如何在iPhone上从后台线程正确调用SQLite函数?

如何在iPhone上从后台线程正确调用SQLite函数?,iphone,objective-c,multithreading,sqlite,Iphone,Objective C,Multithreading,Sqlite,我正在iPhone应用程序中使用SQLite数据库。在启动时,我希望在单独的线程中执行一些数据库操作。(我这样做主要是为了减少启动时间。) 偶尔/随机地,当从后台线程进行这些数据库调用时,应用程序将因以下错误而崩溃: 2009-04-13 17:36:09.932 Action Lists[1537:20b] *** Assertion failure in -[InboxRootViewController getInboxTasks], /Users/cperry/Dropbox/Proje

我正在iPhone应用程序中使用SQLite数据库。在启动时,我希望在单独的线程中执行一些数据库操作。(我这样做主要是为了减少启动时间。)

偶尔/随机地,当从后台线程进行这些数据库调用时,应用程序将因以下错误而崩溃:

2009-04-13 17:36:09.932 Action Lists[1537:20b] *** Assertion failure in -[InboxRootViewController getInboxTasks], /Users/cperry/Dropbox/Projects/iPhone GTD/GTD/Classes/InboxRootViewController.m:74
2009-04-13 17:36:09.932 Action Lists[1537:3d0b] *** Assertion failure in +[Task deleteCompletedTasksInDatabase:completedMonthsAgo:], /Users/cperry/Dropbox/Projects/iPhone GTD/GTD/Classes/Data Classes/Task.m:957
2009-04-13 17:36:09.933 Action Lists[1537:20b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error: failed to prepare statement with message 'library routine called out of sequence'.'
2009-04-13 17:36:09.933 Action Lists[1537:3d0b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error: failed to prepare statement with message 'library routine called out of sequence'.'
虽然我不能可靠地再现错误,但我确信这是由于两个活动线程都在调用SQLite函数。我应该如何从单独的线程调用SQLite函数?我错过了什么把戏吗?我对iPhone、SQLite和Objective-C还很陌生,所以这对你来说可能很明显,但对我来说却不那么明显

下面是一些代码示例

main application.m:

- (void)applicationDidFinishLaunching:(UIApplication *)application {

    // Take care of jobs that have to run at startup
    [NSThread detachNewThreadSelector:@selector(startUpJobs) toTarget:self withObject:nil];
}

// Jobs that run in the background at startup
- (void)startUpJobs {

    // Anticipating that this method will be called in its NSThread, set up an autorelease pool.
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    // Get user preferences
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    // This Class Method calls SQLite functions and sometimes causes errors.
    [Task revertFutureTasksStatus:database];


    [pool release];
}
任务m:

static sqlite3_stmt *revert_future_statement = nil;

+ (void) revertFutureTasksStatus:(sqlite3 *)db {

    if (revert_future_statement == nil) {
        // Find all tasks that meet criteria
        static char *sql = "SELECT task_id FROM tasks where ((deleted IS NULL) OR (deleted=0)) AND (start_date > ?) AND (status=0) AND (revert_status IS NOT NULL)";
        if (sqlite3_prepare_v2(db, sql, -1, &revert_future_statement, NULL) != SQLITE_OK) {
            NSAssert1(0, @"Error: failed to prepare update statement with message '%s'.", sqlite3_errmsg(db));
        }
    }

    // Bind NOW to sql statement
    NSDate *now = [[NSDate alloc] init];
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"yyyy-MM-dd"];
    NSString *nowString = [formatter stringFromDate:now];
    sqlite3_bind_text(revert_future_statement, 1, [nowString UTF8String], -1, SQLITE_TRANSIENT);
    [now release];
    [formatter release];

    // We "step" through the results - once for each row.
    while (sqlite3_step(revert_future_statement) == SQLITE_ROW) {

        // Do things to each returned row

    }

    // Reset the statement for future reuse.
    sqlite3_reset(revert_future_statement);
}

SQLite句柄(
sqlite3\u stmt*
当然,我认为
sqlite3*
是线程特定的。从多个线程调用它们的正确方法是为每个线程维护一组单独的句柄。

该错误消息映射到SQLITE\u误用(源代码可访问)

有关从多个线程使用sqlite3*数据库句柄的限制,请参阅。实际上,您可以跨线程重用数据库句柄和语句,但必须在另一个线程启动之前完全完成一个线程对数据库的访问(即,重叠访问是不安全的)。这听起来像是发生在您身上的事情,并且与SQLITE_误用错误代码一致


如果需要从多个线程访问同一数据库,我建议您分别从每个线程打开数据库,并使用sqlite3\u busy\u timeout()设置超时。Sqlite随后将为您处理争用,如果另一个线程正在写入数据,而同时仍允许同时读取,则会在一个线程中阻塞一段时间。

我将使用NSOperation,并在启动期间在那里执行所有操作。岩石。我说了多少钱了吗?是的。摇滚乐,就是这样。

我已经尝试了这两种解决方案,它们非常有效。您可以使用critical Section或NSOperationQueue,我更喜欢第一个,以下是这两个部分的代码:

定义某个类“DatabaseController”,并将此代码添加到其实现中:

static NSString * DatabaseLock = nil;
+ (void)initialize {
    [super initialize];
    DatabaseLock = [[NSString alloc] initWithString:@"Database-Lock"];
}
+ (NSString *)databaseLock {
    return DatabaseLock;
}

- (void)writeToDatabase1 {
    @synchronized ([DatabaseController databaseLock]) {
        // Code that writes to an sqlite3 database goes here...
    }
}
- (void)writeToDatabase2 {
    @synchronized ([DatabaseController databaseLock]) {
        // Code that writes to an sqlite3 database goes here...
    }
}
或者要使用NSOperationQueue,您可以使用:

static NSOperationQueue * DatabaseQueue = nil;
+ (void)initialize {
    [super initialize];

    DatabaseQueue = [[NSOperationQueue alloc] init];
    [DatabaseQueue setMaxConcurrentOperationCount:1];
}
+ (NSOperationQueue *)databaseQueue {
    return DatabaseQueue;
}

- (void)writeToDatabase {
    NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(FUNCTION_THAT_WRITES_TO_DATABASE) object:nil];
    [operation setQueuePriority:NSOperationQueuePriorityHigh];
    [[DatabaseController databaseQueue] addOperations:[NSArray arrayWithObject:operation] waitUntilFinished:YES];
    [operation release];
}

这两个解决方案阻塞了当前线程直到完成数据库的写入,在大多数情况下你可以考虑。

< P>如果你仍然没有上面的运气,你可以尝试使用这个包装器

它们使用异步回调,这可能一举两得

请看一下我的自述部分
EGODatabaseRequest-对db的异步请求

我的解决方案是删除设备上的应用程序(我想删除我创建的数据库)。这就解决了我的问题。

如果您想在多个线程上无限制地使用SQLite,请在打开连接之前执行以下操作:

sqlite3_shutdown()
sqlite3_配置(SQLITE_配置_序列化)
sqlite3_初始化()


最好使用GCD(Grand Central Dispatch)队列来防止同时访问sqlite数据库

使用任何形式的锁定(包括多个数据库实例使用的文件锁定)都可能会导致繁忙的等待,这是浪费时间的


参见类似的问题。

您应该真正考虑使用队列——或者通过GCD调度函数或NS操作。创建命名队列会创建一类工作的简单同步。它也会遇到同样的问题。也总是抛出错误21。你太棒了!第一个解决方案就是。。。太神了