rraallvv
rraallvv

Reputation: 2943

monitoring ifstream read progress from separate thread in Obj-C

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

Answers (3)

Jody Hagins
Jody Hagins

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

rraallvv
rraallvv

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

gnasher729
gnasher729

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

Related Questions