【问题标题】:GCD, Threads, Program Flow and UI UpdatingGCD、线程、程序流程和 UI 更新
【发布时间】:2011-11-09 14:15:56
【问题描述】:

我很难弄清楚如何将这一切放在一起。 我在 mac 上有一个解谜应用程序。 你进入谜题,按下一个按钮,当它试图找到解决方案的数量时, 分钟移动等我想保持 UI 更新。 计算完成后,重新启用按钮并更改标题。

下面是按钮选择器的一些示例代码,以及求解函数: (请记住我是从 Xcode 复制/粘贴的,所以可能会丢失一些 {} 或 其他一些错别字.. 但它应该让你知道我想要做什么。

基本上,用户按下一个按钮,该按钮是 ENABLED=NO,调用函数来计算谜题。在计算时,请使用移动/解决方案数据更新 UI 标签。 然后一旦完成计算拼图,Button 为 ENABLED=YES;

按下按钮时调用:

- (void) solvePuzzle:(id)sender{
    solveButton.enabled = NO;
    solveButton.title = @"Working . . . .";

    // I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
    [self performSelectorInBackground:@selector(createTreeFromNode:) withObject:rootNode];

    // I've tried to use GCD but similar issue and can't get UI updated.
    //dispatch_queue_t queue = dispatch_queue_create("com.gamesbychris.createTree", 0);
    //dispatch_sync(queue, ^{[self createTreeFromNode:rootNode];});

    }

    // Need to wait here until createTreeFromNode is finished.
    solveButton.enabled=YES;
    if (numSolutions == 0) {
    solveButton.title = @"Not Solvable";
    } else {
        solveButton.title = @"Solve Puzzle";
    }
}

需要在后台运行,以便更新 UI:

-(void)createTreeFromNode:(TreeNode *)node
{
   // Tried using GCD
   dispatch_queue_t main_queue = dispatch_get_main_queue();

 ...Create Tree Node and find Children Code...

if (!solutionFound){
    // Solution not found yet so check other children by recursion.
   [self createTreeFromNode:newChild];
   } else {
   // Solution found.
   numSolutions ++;
   if (maxMoves < newChild.numberOfMoves) {
       maxMoves = newChild.numberOfMoves;
    }
    if (minMoves < 1 || minMoves > newChild.numberOfMoves) {
        solutionNode = newChild;
        minMoves = newChild.numberOfMoves;

        // Update UI on main Thread

        dispatch_async(main_queue, ^{
                        minMovesLabel.stringValue = [NSString stringWithFormat:@"%d",minMoves];
                        numSolutionsLabel.stringValue = [NSString stringWithFormat:@"%d",numSolutions];
                        maxMovesLabel.stringValue = [NSString stringWithFormat:@"%d",maxMoves];
                    });
                }                        

【问题讨论】:

    标签: iphone objective-c ios multithreading macos


    【解决方案1】:

    GCD 和 performSelectorInBackground 示例如下。但首先,让我们看看您的代码。

    您不能在上面的代码中等待。 这是你的代码。你在评论中说等待的地方是不正确的。看看我在哪里添加了 NO。

    - (void) solvePuzzle:(id)sender{
        solveButton.enabled = NO;
        solveButton.title = @"Working . . . .";
    
        // I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
        [self performSelectorInBackground:@selector(createTreeFromNode:) withObject:rootNode];
    
        // NO - do not wait or enable here.
        // Need to wait here until createTreeFromNode is finished.
        solveButton.enabled=YES;
    
    }
    

    UI 消息循环在保持 UI 运行的主线程上运行。 solvePuzzle 在主线程上被调用,所以你不能等待 - 它会阻塞 UI。它也无法将按钮重新设置为启用 - 工作尚未完成。

    后台线程上的工作函数的工作是完成工作,然后在完成后更新 UI。但是您不能从后台线程更新 UI。如果你没有使用块并使用 performSelectInBackground,那么当你完成后,调用 performSelectorOnMainThread 调用选择器来更新你的 UI。

    performSelectorInBackground 示例:

    在这个 sn-p 中,我有一个调用长时间运行的工作的按钮、一个状态标签,我添加了一个滑块以显示我可以在 bg 工作完成时移动滑块。

    // on click of button
    - (IBAction)doWork:(id)sender
    {
        [[self feedbackLabel] setText:@"Working ..."];
        [[self doWorkButton] setEnabled:NO];
    
        [self performSelectorInBackground:@selector(performLongRunningWork:) withObject:nil];
    }
    
    - (void)performLongRunningWork:(id)obj
    {
        // simulate 5 seconds of work
        // I added a slider to the form - I can slide it back and forth during the 5 sec.
        sleep(5);
        [self performSelectorOnMainThread:@selector(workDone:) withObject:nil waitUntilDone:YES];
    }
    
    - (void)workDone:(id)obj
    {
        [[self feedbackLabel] setText:@"Done ..."];
        [[self doWorkButton] setEnabled:YES];
    }
    

    GCD 示例:

    // on click of button
    - (IBAction)doWork:(id)sender
    {
        [[self feedbackLabel] setText:@"Working ..."];
        [[self doWorkButton] setEnabled:NO];
    
        // async queue for bg work
        // main queue for updating ui on main thread
        dispatch_queue_t queue = dispatch_queue_create("com.sample", 0);
        dispatch_queue_t main = dispatch_get_main_queue();
    
        //  do the long running work in bg async queue
        // within that, call to update UI on main thread.
        dispatch_async(queue, 
                       ^{ 
                           [self performLongRunningWork]; 
                           dispatch_async(main, ^{ [self workDone]; });
                       });    
    }
    
    - (void)performLongRunningWork
    {
        // simulate 5 seconds of work
        // I added a slider to the form - I can slide it back and forth during the 5 sec.
        sleep(5);
    }
    
    - (void)workDone
    {
        [[self feedbackLabel] setText:@"Done ..."];
        [[self doWorkButton] setEnabled:YES];
    }
    

    【讨论】:

    • 很好的解释。但我有一个问题..说我的长期工作,(在这种情况下是你的睡眠语句)需要递归吗?我可以从内部再次调用 performLongRunningWork 而不会导致 performSelectorOnMainThread workDone 语句出现问题吗?那最终不会像递归一样多次调用工作吗?我想在这个例子中,它只是设置按钮属性,但说 workDone 做得更多。
    • 在第一个带有 performSelectorInBackground 的示例中,你真的不能,因为它调用了在该函数中更新 UI。当递归完成时,您必须跟踪和调节调用和调节......但是......
    • 但是,使用 GCD 示例,它更干净 - performWork 可以做任何它想做的事情。当它返回时,它将在主线程上调用 workDone。
    • 好的,谢谢!我使用了 GCD 示例。效果很好!非常感谢.. 我想我现在明白如何正确使用 GCD 和块了。非常感谢。
    • 很好的答案,谢谢布莱恩。我赏金!任何有兴趣的人的相关微妙问题..stackoverflow.com/q/25014581/294884
    【解决方案2】:
      dispatch_queue_t backgroundQueue;
    
      backgroundQueue = dispatch_queue_create("com.images.bgqueue", NULL);        
    
    
        - (void)process {    
        dispatch_async(backgroundQueue, ^(void){
        //background task
            [self processHtml];
        dispatch_async(main, ^{ 
    // UI updates in main queue
       [self workDone]; 
        });
    
        });  
        });    
     }
    

    【讨论】:

      【解决方案3】:

      总的来说,任何要提交到后台队列的工作都需要遵循以下代码模式:

      dispatch_queue_t queue = dispatch_queue_create("com.myappname", 0);
      
      __weak MyClass  *weakSelf = self;  //must be weak to avoid retain cycle
      
      //Assign async work
      dispatch_async(queue, 
      ^{ 
         [weakSelf doWork]; 
         dispatch_async(dispatch_get_main_queue(), 
         ^{ 
            [weakSelf workDone]; 
          });
       }); 
       queue = nil;  //Using ARC, we nil out. Block always retains the queue.
      

      永远不要忘记:

      1 - 上面的队列变量是reference counted object,因为它是私有队列,而不是全局队列。所以它被在该队列中执行的块保留。在此任务完成之前,它不会被释放。

      2 - 每个队列都有自己的堆栈,将作为递归操作的一部分进行分配/释放。您只需要担心作为上述 doWork 的一部分访问的被引用计数的类成员变量(强、保留等)。

      3 - 在后台队列操作中访问那些引用计数的变量时,您需要使它们成为线程安全的,具体取决于应用程序中的用例。示例包括对字符串、数组等对象的写入。这些写入应封装在 @synchronized 关键字内,以确保线程安全访问。

      @synchronized 确保在它封装的块被执行期间,没有其他线程可以访问它所保护的资源。

      @synchronized(myMutableArray)
      {
          //operation
      }
      

      在上述代码块中,不允许任何其他线程对@synchronized 块内的myMutableArray 进行更改。

      【讨论】:

      • 你正在创建一个串行队列,它保证了任务执行的顺序。这使您的 @synchronized 块变得多余。请参阅并发编程指南:developer.apple.com/library/ios/documentation/general/…
      • 主队列与串行队列竞争修改一些东西怎么样?串行队列是否保证在主队列接管之前完成?
      • 嗨@quellish,嗯,我也没有关注你,你能详细说明一下吗? Nirav 正在调度异步(注意“a”)对吗?
      • @JoeBlow,他正在将块入队以在串行队列上异步执行,并提供 附加 锁(@synchronized)。因为是串行队列,所以不需要额外的锁。请参阅有关编写无锁代码的并发编程指南部分。使用串行队列来保护对阵列的访问取代了锁。
      • 啊-TBC,他正在调度队列“com.myappname”,0。这将是一个串行队列,你是说。所以,现在说得通了!谢谢!!
      猜你喜欢
      • 1970-01-01
      • 2013-08-02
      • 2012-12-31
      • 1970-01-01
      • 1970-01-01
      • 2013-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多