2009-04-13 25 views
6

Tôi đang sử dụng cơ sở dữ liệu SQLite trong ứng dụng iPhone của mình. Khi khởi động, có một số hành động cơ sở dữ liệu mà tôi muốn thực hiện trong một luồng riêng biệt. (. Tôi đang làm điều này chủ yếu là để giảm thiểu thời gian khởi động)Làm cách nào để gọi đúng chức năng SQLite từ chuỗi nền trên iPhone?

thoảng/ngẫu nhiên, khi các cuộc gọi cơ sở dữ liệu được làm từ các sợi nền, ứng dụng sẽ sụp đổ với các lỗi này:

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

Mặc dù tôi có thể không thể tái tạo một cách đáng tin cậy lỗi, tôi đã thuyết phục bản thân rằng đó là do thực tế rằng các hàm SQLite đang được gọi trong cả hai chuỗi hoạt động. Làm thế nào nên Tôi đang gọi các hàm SQLite từ một chuỗi riêng biệt? Có một mẹo tôi đang thiếu? Tôi khá mới đối với iPhone, SQLite và Objective-C, vì vậy nó có thể là thứ gì đó hiển nhiên đối với bạn, nhưng không phải như vậy đối với tôi.

Dưới đây là một số mẫu mã.

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

Bạn thực sự nên xem xét việc sử dụng hàng đợi - thông qua chức năng gửi GCD hoặc NSOperation. Tạo một hàng đợi có tên tạo ra sự đồng bộ hóa đơn giản của một lớp công việc. – bryanmac

Trả lời

6

Thông báo lỗi đó ánh xạ tới SQLITE_MISUSE (mã nguồn có sẵn tại http://www.sqlite.org).

Xem http://www.sqlite.org/faq.html#q6 để biết các hạn chế về việc sử dụng bộ xử lý cơ sở dữ liệu sqlite3 * từ nhiều hơn một chuỗi. Có hiệu quả, bạn được phép sử dụng lại một xử lý cơ sở dữ liệu và các câu lệnh trên luồng nhưng một luồng phải được thực hiện hoàn toàn khi truy cập cơ sở dữ liệu trước khi bắt đầu luồng khác (tức là truy cập chồng chéo không an toàn). Điều đó nghe có vẻ như những gì đang xảy ra với bạn và nhất quán với mã lỗi SQLITE_MISUSE.

Nếu bạn cần truy cập cùng một cơ sở dữ liệu từ nhiều hơn một luồng, tôi khuyên bạn nên mở cơ sở dữ liệu riêng biệt với từng luồng và đặt thời gian chờ bằng sqlite3_busy_timeout(). Sqlite sau đó sẽ xử lý tranh chấp cho bạn, chặn trong một thời gian ngắn trong một luồng nếu luồng khác đang ghi dữ liệu trong khi vẫn cho phép đọc đồng thời.

1

xử lý SQLite (sqlite3_stmt * chắc chắn, và sqlite3 * tôi nghĩ) là chủ đề cụ thể. Cách chính xác để gọi chúng từ nhiều luồng là duy trì một bộ xử lý riêng biệt cho mỗi luồng.

0

Tôi sẽ sử dụng NSOperation và chỉ làm mọi thứ ở đó trong khi khởi động. Đá NSOperation. Tôi đã nói bao nhiêu NSOperation đá? Nó có. Rock, đó là.

8

Tôi đã thử hai giải pháp này và chúng hoạt động hoàn hảo. Bạn có thể sử dụng phần quan trọng hoặc NSOperationQueue và tôi thích cái đầu tiên, đây là mã cho cả hai trong số họ:

xác định một số lớp "DatabaseController" và thêm mã này vào thực hiện:

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

HOẶC sử dụng NSOperationQueue bạn có thể sử dụng:

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

hai giải pháp này chặn các thread hiện hành cho đến khi bằng văn bản cho cơ sở dữ liệu được hoàn tất mà bạn có thể xem xét ở hầu hết các trường hợp.

+0

cảm ơn bạn! giải pháp tuyệt vời – Jean

+0

bạn đá! giải pháp đầu tiên chỉ là ... tuyệt vời! – sztembi

0

Nếu bạn vẫn không có bất kỳ may mắn với ở trên, bạn có thể thử sử dụng wrapper này bằng cách Enormego https://github.com/jdp-global/egodatabase

Họ sử dụng callbacks không đồng bộ có thể giết hai con chim với một đá.

Hãy nhìn vào phần Readme của tôi cho EGODatabaseRequest - yêu cầu không đồng bộ để db

+0

Nó bị các vấn đề tương tự. Lỗi throws 21 tất cả các thời gian quá. – Pripyat

-1

Giải pháp của tôi là để xóa các ứng dụng trên điện thoại của tôi (tôi muốn xóa cơ sở dữ liệu mà tôi tạo ra). Điều này giải quyết vấn đề cho tôi.

1

Nếu bạn muốn sử dụng SQLite trên nhiều chủ đề không có giới hạn, làm như sau trước khi mở kết nối của bạn:

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

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

0

Tốt nhất là sử dụng GCD (Grand Central Dispatch) hàng đợi để ngăn chặn truy cập đồng thời vào cơ sở dữ liệu SQLite.

Sử dụng bất kỳ hình thức khóa nào (bao gồm khóa tệp sẽ được nhiều trường hợp cơ sở dữ liệu sử dụng) có thể gây ra sự chờ đợi bận rộn lãng phí.

Xem my answer cho một câu hỏi tương tự.

Các vấn đề liên quan