【问题标题】:NSTimer in objective c blocking main thread - ipad programmingObjective c 中的 NSTimer 阻塞主线程 - ipad 编程
【发布时间】:2012-11-27 00:22:38
【问题描述】:

我在 Objective-C 中使用NSTimer 定期(1 分钟间隔)运行后台进程,该进程检查任何未保存的数据并通过 Web 服务提交到数据库。此过程运行良好,不会阻塞主应用程序的流程,直到重新启动 Web 服务器的特定场景。

当网络服务器启动时,后台进程正在尝试连接到服务器并一直挂在那里,直到服务器完全启动并可以访问。当它挂断时,它还阻塞了主要应用程序流并冻结了整个屏幕。当网络服务器完全启动时,挂起的进程恢复并且主应用程序也恢复工作。

谁能告诉我为什么在后台进程挂起时主应用程序会间歇性冻结。

以下代码启动定时器

- (void) startSync{
    syncTimer = [NSTimer scheduledTimerWithTimeInterval:60
                                         target:self
                                       selector:@selector(syncExtSys:)
                                       userInfo:nil
                                        repeats:YES];
}

以下方法每 60 秒调用一次,saveAgendaToDb 方法调用会调用 Web 服务:

- (void) syncExtSys:(NSTimer *) theTimer{
    UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:[NSBundle mainBundle]];
    AgendaItemViewController *agendaItemViewController = [storyboard instantiateViewControllerWithIdentifier:@"AgendaItemViewController"];
    RollCallViewController *rollCallViewController = [storyboard instantiateViewControllerWithIdentifier:@"RollCallViewController"];

    for(int i=0;i<agenda.count;i++){
        unsavedAgenda = [agenda objectAtIndex:i];
        if(unsavedAgenda !=nil && [unsavedAgenda.dirtyFlag isEqualToString:@"Y"] && [self checkNetworkStatus]){

            NSLog(@"agenda out of sync = %@ %@",unsavedAgenda.measureType,unsavedAgenda.measureNum);
            [agendaItemViewController saveAgendaToDb:[agendaItemViewController createDictForAgenda:unsavedAgenda]:unsavedAgenda];
        }
    }
    for(int i=0;i<_rollCall.count;i++){
        Member *member = [_rollCall objectAtIndex:i];

        if(member !=nil && [member.dirtyFlag isEqualToString:@"Y"] && [self checkNetworkStatus]){

            [rollCallViewController saveRollCall:[rollCallViewController createRollCall]];
            break;
        }
    }

    [self calcDirtyFlag];
}

调用网络服务的代码

-(void) saveAgendaToDb:(NSMutableDictionary*) agendaDictionary:(AgendaItem*) agenda{
    NSError *error;
    CommVotesAppDelegate *delegate = (CommVotesAppDelegate *)[[UIApplication sharedApplication] delegate];
    @try{

        NSString *postUrlString = [NSString stringWithFormat:@"%@%@:%@%@%@/%@",@"http://",delegate.prop.serverName,
                                   delegate.prop.port,delegate.prop.urlPattern,delegate.prop.saveAgendaMethod,delegate.prop.objectType];
        NSURL *postUrl = [NSURL URLWithString:postUrlString];
        NSMutableURLRequest *urlRequestPost = [[NSMutableURLRequest alloc] initWithURL:postUrl];
        NSData *postData = [NSJSONSerialization dataWithJSONObject:agendaDictionary options:0 error:&error];
        [urlRequestPost setHTTPMethod:@"POST"];
        [urlRequestPost setValue:@"text/plain" forHTTPHeaderField:@"Accept"];
        [urlRequestPost setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        [urlRequestPost setHTTPBody: postData];
        // NSLog(@"After calling webservices");
        NSData *returnData = [ NSURLConnection sendSynchronousRequest: urlRequestPost returningResponse: nil error: &error ];
        NSString *returnString = [[NSString alloc] initWithData:returnData encoding: NSUTF8StringEncoding];
        NSLog(@"Response after calling save agenda web service = %@",returnString);
       [delegate dirtyFlagCalc:returnString :[error localizedDescription]:agenda];
    }@catch(NSException *nserror){
      NSLog(@"Error while sending agenda through webservices = %@",nserror.debugDescription);
    }@finally {
        NSLog(@"Inside finally block of agenda saving ");
        [delegate saveDataToDisk];
    }
}

【问题讨论】:

  • 显示后台进程的代码。
  • 我已经编辑了这个问题。请看代码。
  • 显示您尝试连接到网络服务的代码。
  • 我已编辑显示执行 web 服务调用的方法。

标签: objective-c multithreading ipad nstimer


【解决方案1】:

计时器在主线程上触发。如果 syncExtSys: 方法没有及时完成,它将挂起主线程。

您的情况是您想做一些可能需要一段时间的事情,但您希望结果显示在主线程的 UI 中。处理此问题的一些好方法是使用 Grand Central Dispatch 执行以下操作之一:

  1. 使用计时器调度源在后台队列上执行处理,然后调度结果以显示在主队列上。
  2. 坚持使用 NSTimer,但将可能在 syncExtSys: 中可能需要一段时间的任何内容分派到后台队列,然后在完成时将结果分派回主队列。
  3. 使用 syncExtSys 中的所有非阻塞调用:以及它调用的所有内容。

【讨论】:

  • 有没有办法在不阻塞主线程的情况下完成这项工作?
  • 我同意这个诊断;在发布的代码中没有任何后台处理的证据。 NSTimer 将其事件泵入当前运行循环,它不会创建新线程。
  • 我添加了一些建议的方法。第二个可能是代码更改最少的。
【解决方案2】:

你的错误是这个电话:sendSynchronousRequest。简而言之就是这样。几乎没有任何理由同步发送 Web 请求 - 现在您知道为什么不应该这样做了。是同步的!这意味着它会在我们等待时阻塞线程——如果等待时间很长,就会有很长的阻塞。这是主线程,所以我们阻塞了用户界面、计时器等所有内容。

只需切换到异步请求并正确使用委托回调,一切都会好起来的。

【讨论】:

  • 在我的场景中,调用需要同步,因为我需要来自 web 服务的成功/失败标志才能继续进行。
  • 对不起,我不相信。 Web 通信从不需要同步。当您使用异步 Web 请求时,响应会通过委托消息返回:developer.apple.com/library/ios/#documentation/Foundation/… 这是标准架构,您应该没有理由不使用它。您需要重新排列代码以适应异步架构。
  • 好的,但是如果应用程序的流程取决于来自 web 服务的响应怎么办。在这种情况下,应用程序必须等待服务的响应,然后才能进入下一个屏幕。这可以使用委托回调方法来完成吗...?
  • 是的,这正是您必须做的。你分阶段进行。委托回调启动下一步(“进入下一个屏幕”)。我再说一遍——这是你应该做的,你应该这样做。否则,您将阻塞主线程,这对您的用户来说是一场灾难(并且设备可能会突然终止您的应用程序)。不要偷懒;不要害怕;做对了。
  • 如果那是预期的方式,那么我会这样做。谢谢你让我知道。我仍处于学习曲线中,所以我并不懒惰和害怕。只是想在做之前先确定自己在做什么。
猜你喜欢
  • 2018-02-16
  • 1970-01-01
  • 1970-01-01
  • 2015-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多