1

我想启动一个在另一个线程上运行的任务“以防万一”,以尽量减少用户稍后必须等待的时间。如果有时间完成,则用户不必等待,但如果尚未完成,则需要等待。

例如,viewDidLoad:当用户按下屏幕上的按钮时,将需要在其中打开一个数据库。如果我等到用户实际按下按钮才打开数据库,则会出现延迟。所以我想早点打开它。由于我不知道打开需要多长时间,也不知道用户点击按钮需要多长时间,所以我需要一种说法,如果其他任务尚未完成,则等待,否则继续.

例如:

@implementation aViewController
- (void) viewDidLoad {
    [self.dbManager openOrCreateDbWithCompletionHandler: ^(NSError *err) {
        if( err ) NSLog( @"There was a problem opening the database" );
    }];
}

- (IBAction) goButtonTouched: (id) sender {
    // Wait here until the database is open and ready to use.
    if( ???DatabaseNotAvailableYet??? ) {
        [self putSpinnerOnScreen];
        ???BlockProgressHereUntilDatabaseAvailable???
        [self takeSpinnerOffScreen];
    }
    // Use the database...
    NSManagedObjectContext *context = [self theDatabaseContext];

    // Build the search request for the attribute desired
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: NSStringFromClass([Destinations class])];
    request.predicate = [NSPredicate predicateWithFormat: @"dId == %@", sender.tag];
    request.sortDescriptors = nil;

    // Perform the search
    NSError *error = nil;
    NSArray *matches = [context executeFetchRequest: request error: &error];

    // Use the search results
    if( !matches || matches.count < 1 ) {
        NSLog( @"Uh oh, got a nil back from my Destination fetch request!" );
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle: @"No Info"
                    message: @"The database did not have information for this selection"
                   delegate: nil
          cancelButtonTitle: @"OK"
          otherButtonTitles: nil];
        [alert show];
    } else {
        MyOtherViewController *movc = [[MyOtherViewContoller alloc] init];
        movc.destDetails = [matches lastObject];
        [self.navigationController pushViewController: movc animated: YES];
    }
}
@end

我希望屏幕上永远不会出现微调器,也不会给用户带来任何延迟,但是,由于我不知道建立数据库连接需要多长时间,所以我必须为它没有准备好做好准备当用户按下按钮时。

我不能在完成时使用回调,openOrCreateDbWithCompletionHandler:因为那时我不想做任何事情,只有当用户按下按钮时。

我考虑过使用信号量,但似乎我只会发出一次信号(在openOrCreateDbWithCompletionHandler:调用的完成处理程序中),但每次按下按钮时都会等待它。这似乎只适用于第一次按下按钮。

我考虑过使用dispatch_group_async()for openOrCreateDbWithCompletionHandler:then dispatch_group_wait()ingoButtonTouched:但由于openOrCreateDbWithCompletionHandler:它在另一个线程上工作并立即返回,我认为不会设置等待状态。

我可以简单地设置一个我自己的标志,比如在 , 之前openOrCreateDbWithCompletionHandler:self.notOpenYet = YES;然后在它的完成处理程序中做self.notOpenYet = NO;,然后在goButtonTouched:替换???DatabaseNotAvailableYet??? self.notOpenYet但是我如何阻止其状态的进展?放入循环和计时器似乎很笨拙,因为我不知道等待是纳秒还是秒。

这似乎是一个很常见的情况,我相信你们都经常做这种事情,而且我的教育很差,但是我搜索了 stackOverflow 和网络,并没有找到一个令人满意的答案。

4

4 回答 4

1

我认为,除非您正在构建自己的事件循环,否则阻塞执行是一个坏习惯,这很少需要。你也不需要在你的情况下做任何 GCD 的事情。感受一下异步。

以下应该有效:

@implementation aViewController
- (void) viewDidLoad {
    self.waitingForDB = NO;
    self.databaseReady = NO;
    [self.dbManager openOrCreateDbWithCompletionHandler: ^(NSError *err) {
            if( err ){ 
                NSLog( @"There was a problem opening the database" )
            }else{
                [self performSelectorOnMainThread:@selector(handleDatabaseReady) withObject:nil waitUntilDone:NO];
            };
    }];
}

- (void)handleDatabaseReady{
    self.databaseReady = YES;
    if(self.waitingForDB){
        [self takeSpinnerOffScreen];
        [self go];
    }

}

- (IBAction) goButtonTouched: (id) sender {
    // Wait here until the database is open and ready to use.
    if( !self.databaseReady ) {
        self.waitingForDB = YES;
        [self putSpinnerOnScreen];
    else{
        [self go];
    }
}

-(void)go{
    // Use the database...
    NSManagedObjectContext *context = [self theDatabaseContext];

    // Build the search request for the attribute desired
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: NSStringFromClass([Destinations class])];
    request.predicate = [NSPredicate predicateWithFormat: @"dId == %@", sender.tag];
    request.sortDescriptors = nil;

    // Perform the search
    NSError *error = nil;
    NSArray *matches = [context executeFetchRequest: request error: &error];

    // Use the search results
    if( !matches || matches.count < 1 ) {
        NSLog( @"Uh oh, got a nil back from my Destination fetch request!" );
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle: @"No Info"
                    message: @"The database did not have information for this selection"
                   delegate: nil
          cancelButtonTitle: @"OK"
          otherButtonTitles: nil];
        [alert show];
    } else {
        MyOtherViewController *movc = [[MyOtherViewContoller alloc] init];
        movc.destDetails = [matches lastObject];
        [self.navigationController pushViewController: movc animated: YES];
    }
}
@end

在主线程上执行调用可以handleDatabaseReady保证在设置/读取新属性时不会出现竞争条件。

于 2013-04-17T14:36:24.127 回答
0

我会带着国旗去。您不想阻止 UI,只需显示微调器并从 goButtonTouched 返回。但是,您确实需要在 openOrCreateDbWithCompletionHandler: 中取消微调器(如果它处于活动状态)。

于 2013-04-17T13:46:20.277 回答
-1

这是一个相当简单的场景。你制作了一个方法来做这些事情。让我们称之为doStuff。从主线程,您调用performSelectorInBackgroundThread:@selector(doStuff). 默认情况下不启用该按钮。在结束时启用它,doStuff以便用户在您准备好之前不会点击它。为了使其更具吸引力,您可以在按钮的位置放置一个微调器,然后在完成时将其替换为按钮doStuff

于 2013-04-17T13:50:23.860 回答
-1

您可以使用许多类和 API 来实现这种事情。您可以NSThread与信号量和事件等同步原语一起使用,以在用户实际按下按钮时等待它完成。您可以使用NSOperation子类(带有NSOperationQueue),并且可以使用 GCD 队列。

我建议您查看Apple的并发编程指南中的一些信息。

在您的情况下,您可能最好将操作添加到 GCD 后台队列dispatch_async,并结合使用信号量,您可以在用户点击按钮时等待。您可以查看问题“我如何等待异步调度的块完成?” 例如。

于 2013-04-17T13:52:34.787 回答