2013-08-19 53 views
11

Tôi hiện đang viết một số tập tin vào đĩa trong thread nền chỉ bằng cách gọiios ghi vào đĩa trên sợi nền

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

tôi đang làm điều này như vậy vì nó sẽ không chặn các chủ đề chính. Câu hỏi của tôi là làm thế nào để đảm bảo an toàn luồng. trong khi thao tác nền này đang được thực hiện, điều gì xảy ra khi tôi cố gắng đọc từ đĩa tệp bằng cách gọi:

[NSData dataWithContentsOfFile:fullPath]; 

Nội dung sẽ bị hỏng? HOẶC thao tác ghi sẽ khóa tệp và thao tác đọc sẽ đợi cho đến khi hoàn tất quá trình ghi?

Trả lời

16

Tôi muốn được xu hướng dispatch_sync hoạt động đọc của mình vào my_queue để đảm bảo an toàn thread (giả định rằng đó là một hàng đợi nối tiếp). Bạn cũng có thể sử dụng bất kỳ khác nhau trong số synchronization tools (chẳng hạn như khóa hoặc chỉ thị @synchronized), nhưng với điều kiện bạn đã thiết lập hàng đợi cho tương tác tệp, sử dụng hàng đợi nối tiếp đó có lẽ là dễ nhất.

Kỹ thuật này, việc sử dụng hàng đợi để điều phối tương tác với tài nguyên được chia sẻ sẽ được thảo luận trong phần Eliminating Lock-Based Code của Hướng dẫn lập trình đồng thời .


Bằng cách này, nếu bạn đang lưu trong một hàng đợi nền (có nghĩa là tiết kiệm hoạt động là đủ để biện minh cho làm việc đó trong nền lẽ chậm) nó có thể là thận trọng để đảm bảo rằng bạn yêu cầu một chút thời gian để hoàn thành các hoạt động trong trường hợp ứng dụng, chính nó, bị gián đoạn (tức là người dùng chạm vào nút home vật lý, một cuộc gọi đến, vv) trong khi hoạt động lưu đang được tiến hành. Bạn làm điều này bằng cách gọi beginBackgroundTaskWithExpirationHandler trước khi bạn cử một pha cứu hoạt động, và gọi endBackgroundTask khi nó được thực hiện:

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

này sẽ đảm bảo rằng bạn tiết kiệm hoạt động có cơ hội chiến đấu để hoàn thành công, ngay cả khi ứng dụng bị gián đoạn.

Và, như Jsdodgers chỉ ra, bạn có thể muốn thực hiện viết nguyên tử.

+0

Đây là giải pháp tôi đã sử dụng. Viết và đọc từ đĩa từ cùng một hàng đợi. Cảm ơn rất nhiều. – wjheng

2

Vì mã của bạn là ngay bây giờ, có, sẽ có sự cố. Điều này là do bạn đang đặt thiết bị không chuyển đổi theo nguyên tắc:

return [content writeToFile:fullPath atomically:NO]; 

Điều gì có nghĩa là thay vì xóa tệp sau đó bắt đầu ghi, nó ghi tệp vào một vị trí tệp tạm thời riêng biệt. Khi tệp được ghi hoàn toàn, sau đó xóa phiên bản cũ của tệp (nếu có) và đổi tên tệp mới thành tên chính xác. Nếu quá trình chuyển không hoàn thành, sẽ không có gì xảy ra và tệp tạm thời sẽ bị xóa.

Vì vậy, nếu bạn thay đổi nguyên tử trong dòng đó thành YES, thì gọi đến dữ liệu đó sẽ trả lại dữ liệu cũ cho đến khi quá trình lưu hoàn tất và bất cứ lúc nào sau đó sẽ đưa bạn dữ liệu mới.

Vì vậy, để làm được điều này, bạn sẽ muốn:

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

Tôi chưa bao giờ nói rằng nó đảm bảo an toàn luồng. Chỉ có nó ngăn cản các tập tin từ trở thành bị hỏng và làm cho nó rất khó (mặc dù không biết chính xác như thế nào không) rằng OP sẽ nhận được lỗi mà ông đã hỏi về. (đó là đối tượng dữ liệu bị hỏng).Tôi không biết điều gì sẽ xảy ra nếu anh ta cố gắng truy cập dữ liệu vào lúc này tên của nó đang được thay đổi (tuy nhiên, phương thức này sẽ được thực hiện). Vì vậy, như bạn và Apple đã chỉ ra, nó không phải là chủ đề an toàn. – Jsdodgers

+0

Ngoài ra, ý nghĩa của nó không phải là chủ đề an toàn là khi đa luồng và thiết lập/nhận cùng một đối tượng trong nhiều luồng, bạn không thể đảm bảo liệu giá trị bạn nhận được sẽ là giá trị trước hoặc sau khi nó được đặt. Tuy nhiên, nguyên tử đảm bảo rằng bất cứ điều gì bạn nhận được, nó sẽ là đối tượng đầy đủ, không bị lo lắng (đó là những gì câu hỏi được yêu cầu). – Jsdodgers

+1

Tôi tin rằng đó là lý do các phương thức 'initWithFile' vv của các đối tượng có một phiên bản nhận một tham số' error'. Nếu dữ liệu đã được thay đổi trong quá trình, nó rất có thể sẽ trả về nil là đối tượng (đó là những gì các tài liệu nói sẽ xảy ra trong trường hợp có lỗi) và giải thích những gì đã xảy ra trong đối tượng 'error' mà bạn truyền vào hơn là cung cấp cho bạn dữ liệu bị hỏng. – Jsdodgers

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