dispatch_io_close не отменяет чтение файла - PullRequest
0 голосов
/ 01 марта 2019

Я читаю файл с dispatch_io_read, но при чтении файла мне может потребоваться отменить его.Исходя из документации, я ожидаю, что смогу использовать dispatch_io_read один раз для чтения от 0 до SIZE_MAX (весь файл), а затем вызвать dispatch_io_close (channel, DISPATCH_IO_STOP), чтобы отменить любое новое чтение.Когда я закрываю канал, чтение продолжается, вплоть до конца файла.Я даже пытался разделить чтение файлов на несколько вызовов io_read, думая, что, возможно, это остановится только на одной из этих границ ... Нет.Он просто никогда не остановится.

https://gist.github.com/swillits/f0abb118be1c253f0623ade0cea2b728

@interface Reader : NSObject

+ (instancetype _Nullable)readerForReadingFileURL:(NSURL *)url error:(NSError **)outError;
- (void)beginWithDataAvailableHandler:(void (^)(dispatch_data_t data))dataAvailableHandler completionHandler:(void (^)(NSError * _Nullable))completionHandler queue:(dispatch_queue_t)queue;
- (void)cancel;

@end


@interface Reader ()
@property (readwrite, copy) NSURL * fileURL;
@property (readwrite, retain) dispatch_queue_t dataAvailableQueue;
@property (readwrite, copy, nullable) void (^dataAvailableHandler)(dispatch_data_t data);
@property (readwrite, copy, nullable) void (^completionHandler)(NSError * _Nullable error);
@property (readwrite, copy, nullable) NSError * error; 
@property (readwrite, atomic) BOOL cancelled;
@end


#define READ_ENTIRE_FILE 0


@implementation Reader
{
    dispatch_io_t _channel;
    int _fileDescriptor;
    uint64_t _fileSize;
}


+ (instancetype _Nullable)readerForReadingFileURL:(NSURL *)url error:(NSError **)outError;
{
    return [[self alloc] initWithFileURL:url error:outError];
}


- (instancetype)initWithFileURL:(NSURL *)url error:(NSError **)outError
{
    if (!(self = [super init])) {
        return nil;
    }

    self.fileURL = url;

    _fileDescriptor = open(url.relativePath.fileSystemRepresentation, (O_RDONLY), (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
    if (_fileDescriptor == -1) {
        if (outError) {
            *outError = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
        }
        return nil;
    }

    NSNumber * size = nil;
    if (![url getResourceValue:&size forKey:NSURLFileSizeKey error:outError]) {
        return nil;
    }
    _fileSize = size.unsignedLongLongValue;

    return self;
}


- (void)beginWithDataAvailableHandler:(void (^)(dispatch_data_t data))dataAvailableHandler completionHandler:(void (^)(NSError * _Nullable))completionHandler queue:(dispatch_queue_t)queue
{
    self.completionHandler = completionHandler;
    self.dataAvailableHandler = dataAvailableHandler;

    _channel = dispatch_io_create(DISPATCH_IO_STREAM, _fileDescriptor, queue, ^(int error) {
        NSError * nserr = nil;

        if (self.error) {
            nserr = self.error;
        } else if (error) {
            nserr = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:(NSInteger)error userInfo:nil];
        }

        close(self->_fileDescriptor);
        self->_fileDescriptor = 0;

        self.completionHandler(nserr);
        self.completionHandler = nil;
        self.dataAvailableHandler = nil;
    });
    assert(_channel);

    dispatch_io_set_low_water(_channel, 1024 * 1024 * 1);
    dispatch_io_set_high_water(_channel, 1024 * 1024 * 4);


    // Maybe splitting the file reading into multiple dispatch_io_read calls will let cancelling stop on one of these boundaries? Nope. It still does not stop until the entire file is read.
    #if READ_ENTIRE_FILE
    size_t chunkSize = SIZE_MAX;
    #else
    size_t chunkSize = 1024 * 1024 * 1;
    #endif

    for (off_t currentOffset = 0; currentOffset < _fileSize; currentOffset += chunkSize) {
        BOOL isLastChunk = (currentOffset + chunkSize >= _fileSize);

        dispatch_io_read(_channel, currentOffset, chunkSize, queue, ^(bool done, dispatch_data_t  _Nullable data, int error) {
            if (data) {
                if (self.error) {
                    NSLog(@"%d", (int)currentOffset);
                }
                NSLog(@"Reader read %zu bytes", dispatch_data_get_size(data));
                self.dataAvailableHandler(data);
            }

            if (error) {
                NSLog(@"%d", error);

                if (error == ECANCELED) {
                    self.error = [[NSError alloc] initWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil];
                } else if (error) {
                    self.error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:(NSInteger)error userInfo:nil];
                }
            }

            // Maybe cancelling in the data handler is necessary? Still doesn't cancel anything
            if (self.cancelled || isLastChunk) {
                dispatch_io_close(self->_channel, DISPATCH_IO_STOP);
                dispatch_io_close(self->_channel, 0); // let's try everything!
            }
        });
    }
}


- (void)cancel
{
    self.cancelled = YES;

    // I'm expecting *this* to cancel everything... It does absolutely nothing in every case I tested.
    dispatch_io_close(self->_channel, DISPATCH_IO_STOP);
}


@end

Документация , этот SO ответ , илюбой другой фрагмент кода, который я смог найти, подсказывает мне, что хотя бы один из этих подходов должен работать.

Я действительно очень смущен этим.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...