2009-04-13 9 views
6

Sto usando un database SQLite nella mia app per iPhone. All'avvio, ci sono alcune azioni del database che voglio eseguire in un thread separato. (. Sto facendo questo principalmente per ridurre al minimo il tempo di avvio)Come chiamare correttamente le funzioni SQLite dal thread in background su iPhone?

Di tanto in tanto/a caso, quando queste chiamate al database sono fatte dal thread in background, l'applicazione si bloccherà con questi errori:

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'.' 

anche se posso Riesco a riprodurre l'errore in modo affidabile, mi sono convinto che sia dovuto al fatto che le funzioni SQLite vengono chiamate in entrambi i thread attivi. Come dovrei Devo chiamare le funzioni SQLite da un thread separato? C'è un trucco che mi manca? Sono abbastanza nuovo per iPhone, SQLite e Objective-C, quindi potrebbe essere qualcosa che è ovvio per te, ma non così ovvio per me.

Ecco alcuni esempi di codice.

MainApplication.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 own 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]; 
} 

Task.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); 
} 
+0

È consigliabile prendere in considerazione l'utilizzo delle code, tramite le funzioni di invio GCD o NSOperation. La creazione di una coda nominata crea una semplice sincronizzazione di una classe di lavoro. – bryanmac

risposta

6

Questo messaggio di errore viene mappato su SQLITE_MISUSE (il codice sorgente è disponibile allo http://www.sqlite.org).

Vedere http://www.sqlite.org/faq.html#q6 per le limitazioni sull'utilizzo di un handle di database sqlite3 * da più thread. In effetti, è consentito riutilizzare un handle di database e le istruzioni attraverso i thread, ma un thread deve essere fatto completamente per accedere al database prima che inizi l'altro thread (ad esempio, l'accesso sovrapposto non è sicuro). Questo suona come quello che sta accadendo per te ed è coerente con il codice di errore SQLITE_MISUSE.

Se è necessario accedere allo stesso database da più thread, è consigliabile invece aprire il database separatamente da ciascun thread e impostare un timeout utilizzando sqlite3_busy_timeout(). Sqlite gestirà quindi il conflitto per te, bloccando per un breve periodo di tempo in un thread se l'altro thread sta scrivendo dati pur consentendo comunque letture simultanee.

1

maniglie SQLite (sqlite3_stmt * di sicuro, e sqlite3 * credo) sono specifici del thread. Il modo corretto per chiamarli da più thread è mantenere un set separato di handle per ogni thread.

0

Vorrei usare NSOperation e fare tutto lì durante l'avvio. NSOperation rocks. Ho detto quanto NSOperation oscilla? Lo fa. Rock, cioè.

8

Ho provato queste due soluzioni e hanno funzionato perfettamente. È possibile utilizzare le sezioni critiche o NSOperationQueue e preferisco il primo, ecco il codice per entrambi:

definire qualche classe "DatabaseController" e aggiungere questo codice alla sua attuazione:

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... 
    } 
} 

O per utilizzare il NSOperationQueue è possibile utilizzare:

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]; 
} 

queste due soluzioni bloccano il thread corrente fino a quando la scrittura nel database è finito, che si può prendere in considerazione nella maggior parte dei casi.

+0

grazie! meravigliosa soluzione – Jean

+0

ti rock! la prima soluzione è semplicemente ... incredibile! – sztembi

0

Se ancora non avendo fortuna con sopra, si potrebbe provare a utilizzare questo wrapper da EnormEGO https://github.com/jdp-global/egodatabase

Usano callback asincroni che possono prendere due piccioni con una fava.

Dai un'occhiata alla mia sezione Leggimi per EGODatabaseRequest - richieste asincrone a DB

+0

Ha gli stessi problemi. Genera sempre l'errore 21. – Pripyat

-1

La mia soluzione è quella di eliminare l'applicazione sul mio dispositivo (voglio eliminare il database che ho creato). Questo risolve il problema per me.

1

Se si desidera utilizzare SQLite su più thread senza restrizioni, effettuare le seguenti operazioni prima di aprire il collegamento:

sqlite3_shutdown();
sqlite3_config (SQLITE_CONFIG_SERIALIZED);
sqlite3_initialize();

http://www.sqlite.org/threadsafe.html

0

La cosa migliore è quella di utilizzare GCD code (Grand Central Dispatch) per impedire l'accesso simultaneo al database SQLite.

L'utilizzo di qualsiasi forma di blocco (incluso il blocco dei file che verrebbe utilizzato da più istanze del database) può causare un'eccessiva attesa che è uno spreco.

Vedere my answer per una domanda simile.

Problemi correlati