7

我有一个方法,有时可以在我的代码中调用。下面是一个非常基本的示例,因为代码处理了 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. 枚举对象
  3. 立即从 enumerateObjects 返回,因为它本身是异步的
  4. 结束对 processImages 的调用

processImages 并没有真正完成,因为 enumerategroups 可能仍在运行,但队列可能已经完成,因为它在 enumerategroups 完成工作之前到达块的末尾。这对我来说似乎是一种可能性?

4

7 回答 7

5

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

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

这是一个例子:

  1. SQ 在线程 X 上运行,发送“In processImages”
  2. 日志打印“在进程中”
  3. 线程 X 上的 SQ,发送“Done with processImages”
  4. SQ 在线程 Y 上运行,发送“In processImages”
  5. 日志打印“essImages\n”

5.之后,NSLog不一定知道打印哪个,3.还是4。

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

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

如果所有的 NSlog 调用都在同一个队列上,你就不应该有这个问题。

于 2013-03-25T17:01:59.743 回答
1

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

从文档中:

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

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

于 2013-03-28T23:41:37.960 回答
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");
});
于 2013-03-25T16:50:03.190 回答
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()
}

这样做的问题:run方法在asynchronous_method完成之前退出,队列中的下一个run方法将运行等等。所以目标没有实现,因为每个asynchronous_method都是并行运行的,毕竟不是串行的。

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

let running = DispatchSemaphore(value: 0)

现在 asynchronous_method 完成它调用“完成”方法:

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

通过将 'running.wait()' 添加到 'run' 方法,信号量可用于序列化 asynchronous_method 的链:

func run() {
    asynchronous_method()

    running.wait() 
}

然后在 completed() 方法中添加 'running.signal()'</p>

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

    running.signal()
}

'run' 中的 running.wait() 将阻止它退出,直到使用 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()
    }
}
于 2019-08-29T14:21:50.690 回答
0

我认为串行队列会等待 [直到] 第一个块完成......

确实如此。但是您的第一个块只是调用enumerateGroupsWithTypes并且文档警告我们该方法异步运行:

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

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

所以,底线,你的队列串行的,但它只是顺序启动一系列异步任务,但显然没有等待那些异步任务完成,违背了串行队列的意图。

所以,如果你真的需要让每个任务等待前面的异步任务,这个问题有很多传统的解决方案:

  1. 使用递归模式。即,编写一个processImage需要处理图像数组的版本,并且:

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

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

选项 1 是最简单的,选项 2 是最优雅的,选项 3 是一个脆弱的解决方案,应该尽可能避免。

于 2019-09-05T16:42:46.977 回答
0

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

因此,您必须使用显式将所有调用包装在 main 方法中,dispatch_async(serializedQueue, ^{})以确保一切都以正确的顺序完成......

于 2015-12-13T17:27:16.300 回答
-2

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

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

于 2013-11-21T16:58:53.597 回答