Reputation: 2943
This is the code I'm using to write and read a file in the background using GCD.
#import "AppDelegate.h"
#import <dispatch/dispatch.h>
#import <iostream>
#import <fstream>
size_t fileSize = 1024 * 1024 * 10;
std::ofstream *osPtr = 0;
std::ifstream *isPtr = 0;
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
const float framerate = 40;
const float frequency = 1.0f/framerate;
[NSTimer scheduledTimerWithTimeInterval:frequency
target:self selector:@selector(doSomething)
userInfo:nil repeats:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
std::ofstream os("myFile", std::ios::binary);
if (os) {
osPtr = &os;
for (int i = 0; i<fileSize; i++) {
os << 'c';
}
osPtr = 0;
os.close();
printf("write done\n");
}
std::ifstream is("myFile", std::ios::binary);
if (is) {
is.seekg(0, std::ifstream::end);
fileSize = (size_t)is.tellg();
is.seekg(0, std::ifstream::beg);
isPtr = &is;
while ( is.good() )
{
char c;
is >> c;
}
isPtr = 0;
is.close();
printf("read done\n");
}
});
}
- (void)doSomething
{
// write file progress indicator
if (osPtr)
printf("%5.1f\n", (float)osPtr->tellp()/fileSize*100.0f);
// read file progress indicator
if (isPtr)
printf("%5.1f\n", (float)isPtr->tellg()/fileSize*100.0f);
}
@end
It writes ok, but when reading big files (5 mb or more) an EXEC_BAD_ACCESS error is thrown, within the streambuf class code.
template <class _CharT, class _Traits>
inline _LIBCPP_INLINE_VISIBILITY
typename basic_streambuf<_CharT, _Traits>::int_type
basic_streambuf<_CharT, _Traits>::sbumpc()
{
if (__ninp_ == __einp_)
return uflow();
return traits_type::to_int_type(*__ninp_++); //<---EXEC_BAD_ACCESS
}
This is the project test.zip
Upvotes: 0
Views: 485
Reputation: 28409
The best way to read and write files asynchronously would be to use the GCD IO functions...
There is a convenience read function (and a similar write function).
void dispatch_read(
dispatch_fd_t fd,
size_t length,
dispatch_queue_t queue,
void (^handler)(dispatch_data_t data, int error));
Your handler
block would be called back each time the system had some data ready to be read, and you could update your progress indicator.
You could use dispatch_after
with the same queue, and they would be automatically seriallized (as long as you used a serial queue).
However, just to be clear: your problem is that you are accessing the stream objects from multiple threads at the same time. One thread is running the queue code block, and another is running your timer call. They are both trying to access the same stream objects. Bad news.
If you want to continue to use your method of IO, you need to serialize access in one of several ways. You can create a class that provides safe access to an IOStream across multiple threads, or you can serialize the access yourself with locks. Both C++ and Obj-C provide many synchronization APIs.
However, there is a very common idiom used in lots of apple code: the delegate.
In your case, a simple progress delegate, with a method that sends the current progress, would suffice. This way the delegate is called from within the context of the long running task, which means you have synchronized access to any shared data.
If you want, you can dispatch any GUI work to the main thread with GCD.
Upvotes: 1
Reputation: 2943
So this would be my approach.
I need to subclass std::streambuf, even when it can be overkill (like it is posted here or here), at least for a feature that should be quite common in multithreaded applications.
Upvotes: 0
Reputation: 52602
Does the documentation of std::of stream say that it is thread safe? I don't think so.
My bet would be that you always get a crash if your progress function is called while the osPtr or isPtr exists. But for small files, the writing/reading is so fast that they are both gone before your progress method is ever called.
Upvotes: 1