GCD串行队列似乎不是串行执行的

时间:2013-03-25 15:25:06

标签: 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
        NSLog(@"In processImages");

        ....

        NSLog(@"Done with processImages");
    });
}

我认为每次调用此方法时我都会得到以下输出... “在processImages” “完成了processImages” “在processImages” “完成了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中?

  1. 致电processImages
  2. enumerateObjects
  3. 立即从enumerateObjects返回,因为它本身就是异步
  4. 结束对processImages的调用
  5. 虽然由于enumerategroups可能仍在运行,但是队列可能已完成,因为它在枚举组完成工作之前到达块的末尾。这对我来说似乎有可能吗?

7 个答案:

答案 0 :(得分:4)

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

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

这是一个例子:

  1. SQ在线程X上运行,发送“In processImages”
  2. 日志打印“In proc”
  3. 线程X上的SQ,发送“使用processImages完成”
  4. SQ在线程Y上运行,发送“In processImages”
  5. 日志打印“essImages \ n”
  6. 5.之后,NSLog不一定知道要打印哪个,3或4。

    如果您绝对需要按时间顺序记录,则需要一个专用的队列进行记录。在实践中,我只使用主队列没有问题:

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

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

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

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

来自文档:

  

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

我不确定你为什么要通过使用串行队列来完成,但如果你只是想阻止同时访问,那么你可以在某处添加一个变量来跟踪我们当前是否枚举或如果您不必担心同步问题,请首先检查一下。 (如果你这样做,也许你应该考虑使用GCD组,但这种情况可能有点过头了。)

答案 3 :(得分:0)

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

所以你必须用main dispatch_async(serializedQueue, ^{})包装main方法中的所有调用,以确保一切都以正确的顺序完成......

答案 4 :(得分:0)

使用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()
}

在这种情况下,autoreleaseFrequency为.workItem以便在每次运行后清理内存。

run方法具有一些常规形式:

func run() {
   asynchronous_method()
}

此问题:运行方法在异步_方法完成之前退出,并且队列中的下一个运行方法将运行,依此类推。因此未实现目标,因为每个异步_方法都是并行运行,而不是串行运行。

使用信号量进行修复。在类中声明

let running = DispatchSemaphore(value: 0)

现在,异步方法已完成,它调用了“完成”方法:

func completed() {
   // some cleanup work etc.
}

通过向“运行”方法中添加“ running.wait()”,信号灯可用于序列化异步方法链:

func run() {
    asynchronous_method()

    running.wait() 
}

然后在completed()方法中添加“ running.signal()”

func completed() {
   // some cleanup work etc.

    running.signal()
}

“ run”中的running.wait()将阻止它退出,直到使用running.signal()的已完成方法发出信号为止,这又会阻止串行队列启动队列中的下一个run方法。这样,异步方法链确实可以串行运行。

所以现在该类具有以下形式:

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

答案 5 :(得分:0)

  

我以为串行队列会一直等到第一个块完成...

是的。但是您的第一个代码段只是调用enumerateGroupsWithTypesthe documentation来警告我们该方法异步运行:

  

此方法是异步的。枚举组时,可能会要求用户确认应用程序对数据的访问;但是,该方法会立即返回。

(FWIW,每当您看到具有block / closure参数的方法时,这都是一个危险信号,表明该方法可能异步执行某些操作。您可以始终参考相关方法的文档并进行确认,就像我们在这里一样。)

因此,最重要的是,您的队列串行的,但是它只是顺序启动一系列异步任务,但是显然不等待这些异步任务完成,从而破坏了串行队列的意图。

因此,如果您确实需要让每个任务等待先前的异步任务,则有许多传统的解决方案可以解决此问题:

  1. 使用递归模式。也就是说,编写processImage的格式表示,该格式需要处理一系列图像,并:

    • 检查是否有要处理的图像;
    • 处理第一张图片;和
    • 完成后(即在完成处理程序块中),从数组中删除第一张图片,然后再次调用processImage
  2. 考虑使用operation queues而不是调度队列。然后,您可以将任务实现为“异步” NSOperation子类。这是包装异步任务的一种非常优雅的方法,如https://stackoverflow.com/a/21205992/1271826所示。

  3. 您可以使用信号量使此异步任务同步运行。 https://stackoverflow.com/a/21205992/1271826中也对此进行了说明。

选项1最简单,选项2最优雅,选项3是一种脆弱的解决方案,如果可以的话,应该避免。

答案 6 :(得分:-1)

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

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