在同一线程上运行代码块

4
我遇到了一个问题,无法解决。如果我的方法签名中有一个带有回调的块参数,并且在我的方法中使用了另一个块的API,则该API会异步执行,而我的代码会继续执行并调用回调方法。这让我的视图控制器知道一切都已经完成,但实际上由于API块的原因往往并不是这样。
由于我的方法总是调用回调方法(并且是异步运行的),我是否可以强制使用块的API调用同步运行? 视图控制器
[self.tagRepo sync:^(BOOL finished) {
    if (finished) {
        NSLog(@"Sync completed.");
        if (self.tagRepo.isSyncing)
            NSLog(@"Syncing continues...");
    }
}];

[标签库同步]

- (void)sync:(void(^)(BOOL finished))completed {
    if (self.isSyncing)
        return;

    NSLog(@"Synchronizing tags...");
    self.isSyncing = YES;

    [[EvernoteNoteStore noteStore] getSyncStateWithSuccess:^(EDAMSyncState *syncState) {
        BOOL performCallback = YES;

        if (syncState.fullSyncBefore > lastSyncTime) {
            [self processSync:syncState];
        } else if (syncState.updateCount > self.lastUpdateCount) {
            [self processSync:syncState];
        } else {
            performCallback = NO; // Block calling the call back when we begin the listTagsWithSuccess async.
            self.clientTags = [[self fetchAll] mutableCopy];
            [[EvernoteNoteStore noteStore] listTagsWithSuccess:^(NSArray *tags) {
                self.serverTags = [tags mutableCopy];
                [self processClientTags]; // Only need to make sure client sends any new tags to the server.

                // invoke call back.
                self.isSyncing = NO;
                if (completed)
                    completed(YES);
            } failure:^(NSError *error) {
                self.isSyncing = NO;
                NSLog(@"Failed to list tags.");
            }];
        }

        self.isSyncing = NO;
        if (completed && performCallback)
            completed(YES);
    } failure:^(NSError *error) {
        self.isSyncing = NO;
        NSLog(@"Failed to process a sync.");

        if (completed)
            completed(NO);
    }];
}

当我调用[sync]方法时,我的NSLog显示回调方法在任何我的processSync方法完成之前被调用。我认为这是因为processSync方法的调用发生在另一个块内,所以当前线程上的完成块继续被调用。我是否使用块不当,或者有没有典型的处理嵌套块的回调方式。我应该尝试通过一些GCD分派在当前线程上运行二级块吗?建立KVO?我尝试使用KVO遇到的问题是,在sync过程中API(Evernote的)中使用的块数量会有所变化,这取决于发生了什么变化。因此很难确定NSNotificationCenter何时发布通知、同步处于什么阶段以及是否已完成/需要执行回调或发布另一个通知。我假设有一个标准的方法来解决这个问题。任何提示都将不胜感激!Johnathon.
更新1
当我调用[[EvernoteNoteStore noteStore] ^listTagsWithSuccess]方法时,将调用以下代码。
- (void)getSyncStateWithSuccess:(void(^)(EDAMSyncState *syncState))success 
                        failure:(void(^)(NSError *error))failure
{
    [self invokeAsyncIdBlock:^id {
        return [[self currentNoteStore] getSyncState:[self authenticationToken]];
    } success:success failure:failure];
}

- (void)listTagsWithSuccess:(void(^)(NSArray *tags))success
                    failure:(void(^)(NSError *error))failure
{
     [self invokeAsyncIdBlock:^id {
        return [[self currentNoteStore] listTags:[self authenticationToken]];
    } success:success failure:failure];
}

- (void)invokeAsyncIdBlock:(id(^)())block
                   success:(void(^)(id))success
                   failure:(void(^)(NSError *error))failure
{
    dispatch_async(self.session.queue, ^(void) {
        id retVal = nil;
        @try {
            if (block) {
                retVal = block();
                dispatch_async(dispatch_get_main_queue(),
                               ^{
                                   if (success) {
                                       success(retVal);
                                   }
                               });
            }
        }
        @catch (NSException *exception) {
            NSError *error = [self errorFromNSException:exception];
            [self processError:failure withError:error];
        }
    });
}

更新2

我提供了processSync方法来展示其他异步操作的使用。在processSync方法内部,我进行了另一个Evernote SDK方法的调用,最终它会调用processTags方法。我省略了processServerTags,因为代码很大,但包括了processClientTags,它基本上与processServerTags中的所有内容相同。因此,我有3-4个嵌套的Evernote SDK异步块在运行。

- (void)processSync:(EDAMSyncState *)syncState {
    BOOL fullSync = NO;
    // If we have never updated, perform full sync.
    if (!self.lastUpdateCount)
        fullSync = YES;

    [[EvernoteNoteStore noteStore] getSyncChunkAfterUSN:self.currentChunkUSN maxEntries:200 fullSyncOnly:NO success:^(EDAMSyncChunk *syncChunk) {
        // Loop, re-grabbing the next chunk
        if (syncChunk.chunkHighUSN < syncChunk.updateCount) {
            // Cache the current sync chunk. Since only so much is handed to us
            // during this hand-shake, we cache and go get more.
            [self cacheSyncChunk:syncChunk];
            self.currentChunkUSN = syncChunk.chunkHighUSN;

            // Fetch more sync chunks.
            [self processSync:syncState];
        } else {
            // Retrieved the last sync chunk, cache it and begin processing.
            [self cacheSyncChunk:syncChunk];
            self.currentChunkUSN = syncChunk.chunkHighUSN;

            // Build list of server tags
            [self processTags];

            // Time stamp ourselves so we know when we last updated.
            self.lastSyncTime = [NSDate endateFromEDAMTimestamp:syncState.currentTime];
            self.lastUpdateCount = syncState.updateCount;
        }
    } failure:^(NSError *error) {
        NSLog(@"Failed to process full sync.");
    }];
}

- (void)processTags {

    // Process the tags found on the server first. We bring down any new tags from the server and cache them.
    // Handles any naming conflicts or duplicate conflicts that come up.
    self.clientTags = [[self fetchAll] mutableCopy];
    [self processServerTags];

    // Process client tags. We check if the client has tags that do not exist on the server and send them.
    [self processClientTags];

    // Delete any expunged tags that we still have cached.
    [self expungeTags];

    NSLog(@"Completed syncing tags.");
}

- (void)processClientTags {
    NSLog(@"Processing client tags - Ensuring server is current with client tags.");
    // Now we compare our local cache to the server, in the event new tags were created.
    // TODO: Test this.
    for (Tag *clientTag in self.clientTags) {
        // Predicate for comparing all client tags against server tags.
        // We compare GUID's and Names. Since we can't have duplicate's of either, we check.
        // It is possible that the client created a Tag (GUID #1) and created it on the server externally (GUID #2) but share the same name.
        // In this case, we have to rename them.
        NSPredicate *compareGuidPredicate = [NSPredicate predicateWithFormat:@"guid == %@", clientTag.guid];

        //Check if this note exists already on the server.
        if (![[self.serverTags filteredArrayUsingPredicate:compareGuidPredicate] count]) {
            // If not, we make sure it was never expunged.
            if ([self.expungedTags containsObject:clientTag.guid])
                continue;

            EDAMTag *serverTag = [[EDAMTag alloc] initWithGuid:nil name:clientTag.name parentGuid:nil updateSequenceNum:0];
            serverTag = [self convertManagedTag:clientTag toEvernoteTag:serverTag convertingOnlyChangedProperties:NO];

            // Check which is newer. If the server is newer, update the client, if the client is newer
            // do nothing. It will be handled later under the processClientTags method.
            [[EvernoteNoteStore noteStore] createTag:serverTag success:^(EDAMTag *tag) {
                NSLog(@"Created new %@ tag on the server.", serverTag.name);
                clientTag.guid = tag.guid;
                NSLog(@"Server GUID %@ assigned to Client GUID %@", tag.guid, clientTag.guid);
                [self saveAllChanges];
            } failure:^(NSError *error) {
                NSLog(@"Failed to create the %@ tag.\n%@", clientTag.name, [error description]);
            }];
        }
    }
    NSLog(@"Client tag processing completed.");
}

在阅读了Rob的回答后,我发现我需要对源代码进行一些重新架构,这对我来说不是什么大问题。对于每个存在异步代码的方法,方法签名都需要包含一个回调块。异步代码将在完成时调用该回调块。

1
一个 block 只是一个功能性的语法结构,就像一个方法或函数一样。仅此而已。由于某些很好的原因,GCD 大量地使用了 block。但是 block 本身与线程无关。 - matt
2
@JohnathonSullinger,听起来你必须要了解线程。"我无法确定进程何时完成"。不,当涉及到线程时,你不需要"等待"。你让它们的进程调用你(通常是移动到主线程)。 - matt
所以在我的视图控制器中,当我在回调块内部时,即使模型中的异步调用尚未完成,通常也会返回成功。如果我收到回调并且第三方仍在运行,那么如何确定是否发生了错误? - Johnathon Sullinger
1
不,按照正确的架构,当成功完成时您会获得回调,并在出错时也会获得回调。如果“模型中正在运行的调用尚未完成”,则您将什么也不会得到。回调意味着一切都已完成。(当然,该架构还可以提供重复的消息来让您了解进度,但如果您收到那个消息,您就知道它是进度消息,而不是完成消息。) - matt
同时,也请阅读苹果公司的并发编程指南,因为其中有很多有趣的方法可以解决你所描述的问题。 - matt
显示剩余12条评论
1个回答

1
如果您在sync中传递的块在processSync方法完成之前被调用,那么这表明processSync本身必须执行某些异步操作。(您已经更新了您的问题,并且看起来确实是这种情况。) 如果事实上是这样的话,那么您需要(a)改变processSync方法本身以接受完成块参数,(b)让sync方法将自己的completed()调用移动到您传递给processSync的块中。这样,您就可以确保只有在真正完成后才会调用completed()
因此,它可能看起来像这样:
- (void)sync:(void(^)(BOOL finished))completed {
    if (self.isSyncing)
        return;

    NSLog(@"Synchronizing tags...");
    self.isSyncing = YES;

    [[EvernoteNoteStore noteStore] getSyncStateWithSuccess:^(EDAMSyncState *syncState) {
        if (syncState.fullSyncBefore > lastSyncTime || syncState.updateCount > self.lastUpdateCount) {
            [self processSync:syncState completionHandler:^(BOOL finished){
                self.isSyncing = NO;
                if (completed)
                    completed(finished);
            }];
        } else {
            self.clientTags = [[self fetchAll] mutableCopy];
            [[EvernoteNoteStore noteStore] listTagsWithSuccess:^(NSArray *tags) {
                self.serverTags = [tags mutableCopy];
                [self processClientTags]; // Only need to make sure client sends any new tags to the server.

                // invoke call back.
                self.isSyncing = NO;
                if (completed)
                    completed(YES);
            } failure:^(NSError *error) {
                self.isSyncing = NO;
                NSLog(@"Failed to list tags.");
                if (completed)
                    completed(NO);
            }];
        }

        // self.isSyncing = NO;
        // if (completed && performCallback)
        //     completed(YES);
    } failure:^(NSError *error) {
        self.isSyncing = NO;
        NSLog(@"Failed to process a sync.");

        if (completed)
            completed(NO);
    }];
}

注意,这样可以消除`performCallback`布尔值,因为我们只需确保所有路径都调用回调函数,在`processSync`的情况下,需要等到`processSync`完成其异步过程后才会延迟调用回调函数。
显然,这假设您将重构`processSync`以使用自己的完成处理程序。
关键是,只有在以下情况之一时才调用完成块:(a)最终异步过程已成功完成;或(b)失败了。但在异步过程完成之前不要调用完成块,必要时进行嵌套,如上所示。

我看到你在问题中更新了 processSync。太好了。每次调用实际上是异步的方法时,请添加一个完成块参数。您可以在每个级别上执行此操作。 - Rob
1
谢谢提供的信息。我更新了原帖,展示了processSync方法和processClientTags方法,这表明我最终得到了嵌套的异步Evernote SDK块。从您的回答中看来,我需要重新设计我的方法,以便将完成情况向上冒泡到起始方法“sync”。这会对我有很大帮助。非常感谢!至于风格方面,我对完成处理程序的东西还有点新,所以不确定命名约定。我同意我的命名不是最好的。我也会采用您的建议。 - Johnathon Sullinger
在我的processClientTags方法示例中,我使用for循环遍历集合。在该循环内部,我可能会调用另一个Evernote SDK异步方法。在evernote块中,我不想调用我的processClientTag完成处理程序,因为我可能还没有完成for循环。在for循环结束时,我将调用我的处理程序;如果evernote异步方法尚未完成,怎么办?如果网络连接失败,阻止标签在服务器上创建,我需要知道它是否失败。 - Johnathon Sullinger
在这种情况下,我应该只是将我的标签排队到可变数组中,在循环外部进行处理,在完成时在异步块内调用处理程序吗?我可以在每次createTag调用后检查队列是否为空,然后调用我的完成处理程序。 - Johnathon Sullinger
顺便说一下,你目前正在返回一个BOOL来表示是否完成。更常见的约定可能是返回一个NSError,其中nil表示没有错误(因此成功完成),而其他内容则表示由于错误而停止,但更重要的是,为什么失败了。在处理这样的多个级别时,原始调用程序可能想知道为什么它没有成功(例如没有互联网连接,某些身份验证错误等)。如果没有像NSError对象这样的东西,当你遇到问题时将很难进行调试。 - Rob
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接