The sequential GCD queue does not seem to execute sequentially

I have a method that can sometimes be called in all my code. The following is a very simple example, since the code processes the images and files from the iphone photo gallery and notes that they are already processed at the end of the method.

@property (nonatomic, assign) dispatch_queue_t serialQueue; .... -(void)processImages { dispatch_async(self.serialQueue, ^{ //block to process images NSLog(@"In processImages"); .... NSLog(@"Done with processImages"); }); } 

I would think that every time this method is called, I get the following result ... "In processImages" "Done using processImages" "In processImages" "Done using processImages", etc ...

but i always get

"In processImages" "In processImages" "Done using processImages" "Done using processImages", etc ...

I thought that the sequential queue would wait until the first block was executed, and then run. It seems to me that it starts the method, then it is called again and is launched before the first call even ends, creating duplicate images that are usually not processed due to the fact that if it is actually executed in series, this method will know that they have already been processed. Perhaps my understanding of serial queues is not specific. Any input? Thanks.

EDIT: MORE The context below is what happens in the block ... Could this cause a problem?

 @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; } 

this object is created only once, and, as far as I can tell, it can never be created again based on my code ... I will run tests to make sure.

UPDATE 2: WHAT I THINK CASE, comment on this if you agree / disagree ....

Obviously, my main problem is that it seems that this block of code is executing at the same time, creating duplicate entries (importing one photo twice), when this was usually not done if it was run in series. When a photo is processed, a dirty bit is applied to it, which skips this image the next time the method is called, but this does not happen, and some images are processed twice. Could this be due to the fact that I am listing the objects in the second queue using enumerategroupswithtypes: inside this serialQueue?

  • call processImages
  • enumerateObjects
  • will immediately return from enumerateObjects since async itself
  • end the call to processImages

processImages is not actually running, although due to the fact that enumerategroups are probably still running, the queue can be completed since it reaches the end of the block before the enumerategroups are done. Does this sound like an opportunity for me?

+6
source share
7 answers

Successive queues will be ABSOLUTELY executed in serial. However, they do not guarantee performance in the same topic.

Assuming you are using the same sequential queue, the problem is that NSLog is NOT guaranteed to display the results in the correct order when called from different threads at the same time.

here is an example:

  • SQ runs on thread X, sends "In processImages"
  • the log prints "In proc"
  • SQ in thread X sends "Done with processImages"
  • SQ runs on thread Y, sends "In processImages"
  • log prints "essImages \ n"

After 5. NSLog does not necessarily know what to print, 3. or 4.

If you absolutely need time-based logging, you need a dedicated queue for logging. In practice, I had no problems using the main queue:

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

If all NSlog calls are in the same queue, you should not have this problem.

+4
source

enumerateGroupsWithTypes:usingBlock:failureBlock: does its asynchronous operation on another thread and calls the blocks passed at that time (in the main thread, I think). Considering this from a different perspective, if he completed everything synchronously by the time the method call was completed, he could simply return an enumerator object for groups, for example, for a simpler API.

From the documentation:

This method is asynchronous. When the groups are listed, the user may be prompted to confirm the access of the application to the data; the method, however, returns immediately. You must do whatever work you need with the assets in enumerationBlock.

I'm not sure why you are trying to execute using a sequential queue, but if you just want to prevent concurrent access, then you can just add a variable somewhere that keeps track of whether we are currently listing or not checking this if you No need to worry about synchronization issues. (If you do this, you might want to study the use of the GCD group, but this may probably be redundant for this situation.)

+1
source

If the question: "Can a sequential queue execute tasks asynchronously?" then the answer is no. If you think this is possible, you should make sure that all tasks are actually performed in one queue. You can add the following line to the block and compare the result:

 dispatch_async(self.serialQueue, ^{ NSLog(@"current queue:%p current thread:%@",dispatch_get_current_queue(),[NSThread currentThread]); 

Make sure you write the NSLog to a block that runs in your queue, and not to enumerateGroupsWithTypes: usingBlock: failureBlock: You can also try to create your own queue as follows

 dispatch_queue_create("label", DISPATCH_QUEUE_SERIAL); 

but I don’t think that something will change

EDIT: By the way, the method

 enumerateGroupsWithTypes:usingBlock:failureBlock: 

is asynchronous, why are you calling it in another queue?

UPDATE 2: I can suggest something like this:

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

I ran into such a problem, and the answer for me was to understand that asynchronous calls from a method in a serialized queue are sent to another queue for processing that is not serialized.

So, you have to transfer all the calls inside the main method with explicit dispatch_async(serializedQueue, ^{}) to make sure everything is done in the correct order ...

0
source

Using Swift and semaphores to illustrate a serialization approach:

Given: a class with the asynchronous run method that will run on several objects at the same time, and the goal is to prevent each of them from starting until it finishes.

The problem is that the run method allocates a lot of memory and uses a lot of system resources, which can cause a lack of memory, among other problems, if too many are started at the same time.

The idea is this: if you use a sequential queue, only one will work at a time, one after another.

Create a sequential queue in global space by class:

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

The 'run method of this class, for which a lot of objects will be created and launched, will be processed in a sequential queue:

 serialGeneratorQueue.async { self.run() } 

In this case, autoreleaseFrequency is the .workItem to clear the memory after each run.

The run method has some general form:

 func run() { asynchronous_method() } 

The problem is this: the run method ends before the asynchronous_method completes, the next run method in the queue starts, etc. Thus, the goal is not achieved, since each asynchronous_method is executed in parallel, and not sequentially.

Use the semaphore to fix it. In class declare

 let running = DispatchSemaphore(value: 0) 

Now asynchronous_method shuts down, calls the method: complete:

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

A semaphore can be used to serialize a chain of asynchronous methods by adding 'running.wait () to the' run method:

 func run() { asynchronous_method() running.wait() } 

And then in the complete () method add 'running.signal ()

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

The running.wait () function in 'run will prevent it from exiting until the signal is completed by the completed method using running.signal (), which in turn prevents the sequential queue from starting the next run method in the queue. Thus, the chain of asynchronous methods will indeed be executed sequentially.

So, now the class has the form:

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

I thought that a sequential queue would wait [until] the first block is done ...

It does. But your first block just calls enumerateGroupsWithTypes and the documentation warns us that the method works asynchronously:

This method is asynchronous. When groups are listed, the user may be asked to confirm the application’s access to the data; the method, however, returns immediately.

(FWIW, whenever you see a method that has a block / closure parameter, this means that the method is probably doing something asynchronously. You can always refer to the corresponding documentation of the methods and confirm, as we have here.)

So, the bottom line is that your queue is sequential, but it only sequentially starts a series of asynchronous tasks, but obviously does not expect these asynchronous tasks to complete, damaging the purpose of the sequential queue.

Therefore, if you really need each task to wait for the previous asynchronous task, there are a number of traditional solutions to this problem:

  1. Use a recursive pattern. That is, write a processImage that processes an array of images and:

    • check for images for processing;
    • process the first image; and
    • when done (i.e. in the completion handler block), remove the first image from the array and then call processImage again.
  2. Instead of sending queues, consider using operation queues . You can then implement your task as an “asynchronous” subclass of NSOperation . This is a very elegant way to package an asynchronous task. This is shown at fooobar.com/questions/108748 / ....

  3. You can use semaphores to make this asynchronous task work synchronously. It is also featured at fooobar.com/questions/108748 / ....

Option 1 is the simplest, option 2 is the most elegant, and option 3 is a fragile solution that should be avoided if possible.

0
source

You can have more than one object, each with its own sequential queue. Tasks sent to any sequential queue are executed sequentially, but tasks sent to different sequential queues will alternate.

Another simple mistake would be to create not a sequential queue, but a simultaneous queue ...

-2
source

All Articles