2013-08-19 21 views
11

Attualmente sto scrivendo alcuni file su disco in thread in background semplicemente chiamandoios scrivere su disco su sfondo filo

dispatch_async(my_queue,^{ 
    [self writeToRoot:filename data:data]; 
}; 

- (BOOL)writeToRoot:(NSString *)path data:(NSData *)content 
{ 
    NSString *fullPath = [[self rootPath] stringByAppendingPathComponent:path]; 

    NSString *pathWithoutFile = [fullPath stringByDeletingLastPathComponent]; 

    BOOL directoryExists = [[NSFileManager defaultManager] fileExistsAtPath:pathWithoutFile]; 

    if (!directoryExists) { 
     NSError *error = nil; 
     [[NSFileManager defaultManager] createDirectoryAtPath:pathWithoutFile 
            withIntermediateDirectories:YES 
                attributes:nil error:&error]; 
     NSParameterAssert(error == nil); 
    } 

    return [content writeToFile:fullPath atomically:NO]; 
} 

Sto facendo questo in modo da non sarebbe bloccare il thread principale. La mia domanda è come garantire la sicurezza dei thread. mentre l'operazione di sfondo è in corso, cosa succede quando provo a leggere dal disco del file chiamando:

[NSData dataWithContentsOfFile:fullPath]; 

si accontenterà corrompere? OPPURE l'operazione di scrittura blocca il file e l'operazione di lettura attenderà che la scrittura sia completata?

risposta

16

sarei propenso a dispatch_sync vostra operazione di lettura al my_queue per garantire la sicurezza dei thread (ammesso che si tratta di una coda di serie). È anche possibile utilizzare uno qualsiasi dei vari synchronization tools (come i blocchi o la direttiva @synchronized), ma dato che hai già impostato la coda per l'interazione tra file, l'utilizzo di tale coda seriale è probabilmente più semplice.

Questa tecnica, di usare una coda per coordinare l'interazione con una risorsa condivisa è discusso nella sezione Eliminating Lock-Based Code della Guida concorrenza di programmazione.


A proposito, se si sta salvando in una coda di sfondo (il che significa che l'operazione di salvataggio è sufficiente a giustificare farlo sullo sfondo presumibilmente lento) potrebbe essere prudente per assicurarsi che si richiede un po 'di tempo per completare l'operazione nel caso in cui l'app stessa venga interrotta (cioè l'utente tocchi il pulsante fisico di casa, arriva una chiamata, ecc.) mentre è in corso l'operazione di salvataggio. A tale scopo, chiamando beginBackgroundTaskWithExpirationHandler prima di spedizione l'operazione di salvataggio, e chiamare endBackgroundTask quando è fatto:

UIApplication *application = [UIApplication sharedApplication]; 

// get background task identifier before you dispatch the save operation to the background 

UIBackgroundTaskIdentifier __block task = [application beginBackgroundTaskWithExpirationHandler:^{ 
    if (task != UIBackgroundTaskInvalid) { 
     [application endBackgroundTask:task]; 
     task = UIBackgroundTaskInvalid; 
    } 
}]; 

// now dispatch the save operation 

dispatch_async(my_queue, ^{ 

    // do the save operation here 

    // now tell the OS that you're done 

    if (task != UIBackgroundTaskInvalid) { 
     [application endBackgroundTask:task]; 
     task = UIBackgroundTaskInvalid; 
    } 
}); 

Questo farà in modo che la vostra operazione di salvataggio ha una possibilità di combattere per completare con successo, anche se l'applicazione viene interrotta.

E, come sottolinea Jsdodgers, probabilmente si desidera eseguire anche una scrittura atomica.

+0

Questa è la soluzione che ho finito per usare. Scrittura e lettura dal disco dalla stessa coda. Molte grazie. – wjheng

2

Dato che il codice è adesso, sì, ci sarà un problema. Ciò è dovuto al fatto che si sta configurando a non trasferire atomicamente:

return [content writeToFile:fullPath atomically:NO]; 

Cosa significa atomicamente è che invece di eliminare il file quindi di iniziare la scrittura, scrive il file in una temporanea, luogo separato, di file. Una volta che il file è stato scritto completamente, cancella la vecchia versione del file (se esistente) e rinomina il nuovo file con il nome corretto. Se il trasferimento non è completato, non accadrà nulla e il file temporaneo dovrebbe essere cancellato.

Quindi se si cambia atomicamente in tale riga su YES, la chiamata a tali dati restituirà i vecchi dati fino al completamento del salvataggio, e in qualsiasi momento in seguito si otterranno i nuovi dati.

Quindi, per fare questo, si vorrebbe:

return [content writeToFile:fullPath atomically:YES]; 
+0

Non ho mai detto che garantisce la sicurezza del filo. Solo che impedisce al file di essere corrotto e rende molto improbabile (anche se sconosciuto esattamente come improbabile) che l'OP riceverà l'errore di cui stava chiedendo informazioni. (che è l'oggetto dati corrotto).Non so cosa succederebbe se cercasse di accedere ai dati nel momento in cui il suo nome viene cambiato (comunque il metodo lo fa). Quindi, come tu e Apple avete sottolineato, non è thread-safe. – Jsdodgers

+0

Inoltre, ciò che intendono con esso non è thread-safe: quando si esegue il multithreading e si imposta/si ottiene lo stesso oggetto in più thread, non si può garantire se il valore ottenuto sarà il valore che aveva prima o dopo che era stato impostato. L'atomicità, tuttavia, garantisce che qualunque cosa tu ottenga, sarà l'oggetto completo, non corrotto (che è ciò che la domanda stava chiedendo). – Jsdodgers

+1

Credo che questa sia la ragione per cui i metodi degli oggetti 'initWithFile' etc hanno una versione che accetta un' error' come parametro. Se i dati sono stati modificati durante il processo, molto probabilmente restituirebbe nil come l'oggetto (che è ciò che i documenti dicono avverrà in caso di errore) e spiegherà cosa è successo nell'oggetto 'error' che hai passato, piuttosto di darti dati corrotti. – Jsdodgers