Ios GCD串行队列似乎没有串行执行

Ios GCD串行队列似乎没有串行执行,ios,queue,grand-central-dispatch,serial-processing,Ios,Queue,Grand Central Dispatch,Serial Processing,我有一个方法,它有时可以在我的代码中调用。下面是一个非常基本的示例,因为代码处理iphone照片库中的图像和文件,并在使用该方法完成后标记它们已处理 @property (nonatomic, assign) dispatch_queue_t serialQueue; .... -(void)processImages { dispatch_async(self.serialQueue, ^{ //block to process images NSL

我有一个方法,它有时可以在我的代码中调用。下面是一个非常基本的示例,因为代码处理iphone照片库中的图像和文件,并在使用该方法完成后标记它们已处理

@property (nonatomic, assign) dispatch_queue_t serialQueue;

....

-(void)processImages
{
    dispatch_async(self.serialQueue, ^{
        //block to process images
        NSLog(@"In processImages");

        ....

        NSLog(@"Done with processImages");
    });
}
我想每次调用这个方法时,我都会得到下面的输出。。。 “进程内图像” “使用processImages完成” “进程内图像” “使用processImages完成” 等等

但我总是会

“进程内图像” “进程内图像” “使用processImages完成” “使用processImages完成” 等等

我以为串行队列会等到第一个块完成后再开始。在我看来,它似乎是在启动该方法,然后再次调用它,并在第一次调用完成之前启动,创建通常不会被处理的图像副本,因为如果它真的连续执行,该方法将知道它们已经被处理。也许我对串行队列的理解并不具体。有什么意见吗?多谢各位

编辑:下面是更多的上下文,这是块中发生的事情…这会导致问题吗

@property (nonatomic, assign) dispatch_queue_t serialQueue;

....

-(void)processImages
{
    dispatch_async(self.serialQueue, ^{
        //library is a reference to ALAssetsLibrary object 

        [library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop)
        {
            [group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop)
            {
             ....
             //Process the photos here
            }];
        failureBlock:^(NSError *error) { NSLog(@"Error loading images from library");
        }];

    });
}

-(id)init
{
    self = [super init];
    if(self)
    {
        _serialQueue = dispatch_queue_create("com.image.queue",NULL);
    }
    return self;
}
这个对象只创建了一次,据我所知,根据我的代码不能再创建一次……不过我会运行测试来确保

更新2:我认为正在发生的事情,如果你同意/不同意,请对此发表评论

显然,我的主要问题是,这段代码似乎是并发执行的,创建了重复的条目(导入同一张照片两次),而如果连续运行,通常不会这样做。处理照片时,会对其应用一个“脏”位,以确保下次调用该方法时跳过此图像,但不会发生这种情况,某些图像会被处理两次。这可能是因为我正在使用enumerategroupswithtypes:在该serialQueue中枚举第二个队列中的对象吗

  • 调用processImages
  • 枚举对象
  • 立即从enumerateObjects返回,因为它本身是异步的
  • 结束对processImages的调用
  • processImages并没有真正完成,这是因为enumerategroups可能仍在运行,但队列可能已经完成了,因为它在enumerategroups完成工作之前到达了块的末尾。对我来说,这似乎是一种可能性?

    如果问题是“串行队列能否异步执行任务?”那么答案是否定的。 如果您认为可以,那么应该确保所有任务都在同一队列上执行。可以在块中添加以下行并比较输出:

    dispatch_async(self.serialQueue, ^{
        NSLog(@"current queue:%p current thread:%@",dispatch_get_current_queue(),[NSThread currentThread]);
    
    确保在队列上执行的块中写入NSLog,而不是在enumerateGroupsWithTypes:usingBlock:failureBlock: 您也可以尝试这样创建队列

    dispatch_queue_create("label", DISPATCH_QUEUE_SERIAL);
    
    但我认为这不会改变任何事情

    编辑: 顺便说一下,方法

    enumerateGroupsWithTypes:usingBlock:failureBlock:
    
    是异步的,为什么要在另一个队列上调用它

    更新2: 我可以这样建议:

    dispatch_async(queue, ^{
        NSLog(@"queue");
    
        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER, *pmutex = &mutex;
        pthread_mutex_lock(pmutex);
    
        ALAssetsLibraryGroupsEnumerationResultsBlock listGroupBlock = ^(ALAssetsGroup *group, BOOL *stop) {
            NSLog(@"block");
            if (group) {
                [groups addObject:group];
            } else {
    
                [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];
                dispatch_async(dispatch_get_current_queue(), ^{
                    pthread_mutex_unlock(pmutex);
                });
            }
            NSLog(@"block end");
        };
    
        [assetsLibrary enumerateGroupsWithTypes:groupTypes usingBlock:listGroupBlock failureBlock:failureBlock];
        pthread_mutex_lock(pmutex);
        pthread_mutex_unlock(pmutex);
        pthread_mutex_destroy(pmutex);
        NSLog(@"queue end");
    });
    

    串行队列绝对会串行执行。但是,它们不能保证在同一线程上执行

    假设您使用的是同一个串行队列,问题在于当从不同线程几乎同时调用NSLog时,不能保证以正确的顺序输出结果

    以下是一个例子:

  • SQ在线程X上运行,发送“进程内图像”
  • 日志打印“在过程中”
  • 线程X上的SQ发送“使用processImages完成”
  • SQ在线程Y上运行,发送“进程内图像”
  • 日志打印“essImages\n”
  • 5.之后,NSLog不一定知道打印哪个,3。或4

    如果您确实需要按时间顺序进行日志记录,则需要一个专用的日志队列。实际上,我只使用主队列没有任何问题:

    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"whatever");
    });
    

    如果所有NSlog调用都在同一队列上,则不应出现此问题。

    枚举组swithtypes:usingBlock:failureBlock:
    在另一个线程上异步工作,并在完成时调用传入的块(我认为是在主线程上)。从另一个角度来看,如果它在方法调用完成时同步完成了所有操作,那么它可以只返回组的枚举器对象,例如,对于更简单的API

    从文件中:

    此方法是异步的。当枚举组时,可能会要求用户确认应用程序对数据的访问;不过,该方法会立即返回。您应该使用enumerationBlock中的资产执行任何需要的工作


    我不确定您为什么要尝试使用串行队列来完成此任务,但是如果您只是想阻止同时访问,那么您可以在某个地方添加一个变量,跟踪当前是否正在枚举,并在不必担心同步问题的情况下,先检查一下。(如果您这样做了,也许您应该考虑使用GCD组,但在这种情况下,这可能有些过分。)

    您可能有多个对象,每个对象都有自己的串行队列。分派到任何单个串行队列的任务都是串行执行的,但分派到不同串行队列的任务绝对是交叉执行的


    另一个简单的错误是不创建串行队列,而是创建并发队列…

    我遇到了这样一个问题,我的答案是认识到来自序列化队列上的方法的异步调用会转到另一个队列进行处理——一个未序列化的队列


    因此,您必须使用显式的
    dispatch\u async(serializedQueue,^{})
    将所有调用封装在main方法中,以确保所有操作都按正确的顺序完成…

    使用Swift和信号量来说明序列化方法:

    给定:一个具有异步“run”方法的类,该方法将同时在多个对象上运行,目标是每个对象在完成之前都不会运行。
    let serialGeneratorQueue: DispatchQueue = DispatchQueue(label: "com.limit-point.serialGeneratorQueue", autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.workItem)
    
    class Generator {
    
        func run() {
             asynchronous_method()
        }
    
        func start() {
    
            serialGeneratorQueue.async {
                self.run()
            }
        }
    
        func completed() {
           // to be called by the asynchronous_method() when done
        }
    }
    
    serialGeneratorQueue.async {
        self.run()
    }
    
    func run() {
       asynchronous_method()
    }
    
    let running = DispatchSemaphore(value: 0)
    
    func completed() {
       // some cleanup work etc.
    }
    
    func run() {
        asynchronous_method()
    
        running.wait() 
    }
    
    func completed() {
       // some cleanup work etc.
    
        running.signal()
    }
    
    class Generator {
    
        let running = DispatchSemaphore(value: 0)
    
        func run() {
             asynchronous_method()
    
             running.wait() 
        }
    
        func start() {
    
            serialGeneratorQueue.async {
                self.run()
            }
        }
    
        func completed() {
           // to be called by the asynchronous_method() when done
    
           running.signal()
        }
    }