2009-08-31 14 views
8

Ho appena introdotto il multithreading nella mia app JUST per far funzionare un UIActivityIndicatorView sciocco. Bene, l'indicatore di attività funziona, va bene - ma ora la mia app si blocca a volte, ea volte no - in condizioni altrimenti controllate ... Ho bisogno di capirlo ma non so da dove cominciare a cercare ...Comuni errori di multithreading per principianti su iPhone

Quindi, quali sono gli errori più comuni che i principianti fanno spesso con il multithreading su iPhone? Si prega di essere specifici nelle risposte. Grazie per il tuo tempo.

UPDATE: Ho aggiunto la mia fonte problematica di riferimento.

//--------------------Where the multithreading starts------------------------ 


-(IBAction)processEdits:(id)sender 
{ 
     //Try to disable the UI to prevent user from launching duplicate threads 
    [self.view setUserInteractionEnabled:NO]; 

     //Initialize indicator (delcared in .h) 
    myIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(155, 230, 20, 20)]; 
    myIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhite; 
    [self.view addSubview:myIndicator]; 
    [self.view bringSubviewToFront:myIndicator]; 
    [myIndicator startAnimating]; 


    //Prepare and set properties of the NEXT modal view controller to switch to 
    controller = [[EndViewController alloc] initWithNibName:@"EndViewController" bundle:nil]; 

    controller.delegate = self; 

    [self performSelectorInBackground:@selector(threadWork:) withObject:nil]; 


} 



//-----------------------------THE THREAD WORK-------------------------------- 


-(IBAction)threadWork:(id)sender{ 

    NSAutoreleasePool * pool; 
    NSString *   status; 

    pool = [[NSAutoreleasePool alloc] init]; 
    assert(pool != nil); 


     //The image processing work that takes time 
    controller.photoImage = [self buildPhoto]; 

    //Stop the UIActivityIndicatorView and launch next modal view 
    [self performSelectorOnMainThread:@selector(stopSpinner:)withObject:nil waitUntilDone:NO]; 

    [pool drain]; 


} 




//-------------------Most of the WORKLOAD called in above thread ------------------------ 



-(UIImage*)buildPhoto 
{ 
    /* 
     This is the work performed in the background thread. Process photos that the user has edited and arrange them into a UIView to be finally flattened out into a new UIImage. Problem: UI usually changes for some reason during this work. 
     */ 

    UIView* photoContainerView = [[UIView alloc] initWithFrame:CGRectMake(0,0,975,1300)]; 
    photoContainerView.backgroundColor = [UIColor whiteColor]; 
    UIImage* purikuraFlattened; 
    int spacerX = 10; 
    int spacerY = 10; 

    switch (myPattern) { 

     case 0: 

      photoContainerView.frame = CGRectMake(0, 0, 320, 427); 
      layoutSingle = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x,photoContainerView.frame.origin.y,320,427)]; 
      [photoContainerView addSubview:layoutSingle]; 
      layoutSingle.image = editPhotoData1; 

      break; 


     case 1: 

      layoutAimg1 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX, photoContainerView.frame.origin.y+spacerY, 427, 320)]; 
      layoutAimg2 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX+427, photoContainerView.frame.origin.y+spacerY, 427, 320)]; 
      layoutAimg3 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX, photoContainerView.frame.origin.y+spacerY+320, 427, 320)]; 
      layoutAimg4 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX+427, photoContainerView.frame.origin.y+spacerY+320, 427, 320)]; 
      layoutAimg5 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX, photoContainerView.frame.origin.y+spacerY+(320*2), 427, 320)]; 
      layoutAimg6 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX+427, photoContainerView.frame.origin.y+spacerY+(320*2), 427, 320)]; 
      layoutAimg7 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX, photoContainerView.frame.origin.y+spacerY+(320*3), 427, 320)]; 
      layoutAimg8 = [[UIImageView alloc] initWithFrame:CGRectMake(photoContainerView.frame.origin.x+spacerX+427, photoContainerView.frame.origin.y+spacerY+(320*3), 427, 320)]; 

      [photoContainerView addSubview:layoutAimg1]; 
      [photoContainerView addSubview:layoutAimg2]; 
      [photoContainerView addSubview:layoutAimg3]; 
      [photoContainerView addSubview:layoutAimg4]; 
      [photoContainerView addSubview:layoutAimg5]; 
      [photoContainerView addSubview:layoutAimg6]; 
      [photoContainerView addSubview:layoutAimg7]; 
      [photoContainerView addSubview:layoutAimg8]; 


      if(myShots == 1){ 

      rotPhoto1 = [self rotateImage:editPhotoData1.size:editPhotoData1]; 

       layoutAimg1.image = rotPhoto1; 
       layoutAimg2.image = rotPhoto1; 
       layoutAimg3.image = rotPhoto1; 
       layoutAimg4.image = rotPhoto1; 
       layoutAimg5.image = rotPhoto1; 
       layoutAimg6.image = rotPhoto1; 
       layoutAimg7.image = rotPhoto1; 
       layoutAimg8.image = rotPhoto1; 



      }else if(myShots == 2){ 


      rotPhoto1 = [self rotateImage:editPhotoData1.size: editPhotoData1]; 
      rotPhoto2 = [self rotateImage:editPhotoData2.size: editPhotoData2]; 

       layoutAimg1.image = rotPhoto1; 
       layoutAimg2.image = rotPhoto2; 
       layoutAimg3.image = rotPhoto2; 
       layoutAimg4.image = rotPhoto1; 
       layoutAimg5.image = rotPhoto1; 
       layoutAimg6.image = rotPhoto2; 
       layoutAimg7.image = rotPhoto2; 
       layoutAimg8.image = rotPhoto1; 


      }else if(myShots == 4){ 

       rotPhoto1 = [self rotateImage:editPhotoData1.size: editPhotoData1]; 
       rotPhoto2 = [self rotateImage:editPhotoData2.size: editPhotoData2]; 
       rotPhoto3 = [self rotateImage:editPhotoData3.size: editPhotoData3]; 
       rotPhoto4 = [self rotateImage:editPhotoData4.size: editPhotoData4]; 

       layoutAimg1.image = rotPhoto1; 
       layoutAimg2.image = rotPhoto2; 
       layoutAimg3.image = rotPhoto3; 
       layoutAimg4.image = rotPhoto4; 
       layoutAimg5.image = rotPhoto1; 
       layoutAimg6.image = rotPhoto2; 
       layoutAimg7.image = rotPhoto3; 
       layoutAimg8.image = rotPhoto4; 


      } 
      break; 

     } 


    UIGraphicsBeginImageContext(photoContainerView.bounds.size); 
    [purikuraContainerView.layer renderInContext:UIGraphicsGetCurrentContext()]; 
    photoFlattened = UIGraphicsGetImageFromCurrentImageContext(); 
    UIGraphicsEndImageContext(); 


    NSEnumerator *enumerator = [[photoContainerView subviews] objectEnumerator]; 
    id object; 

    while ((object = [enumerator nextObject])) { 

     [object removeFromSuperview]; 

    } 


    [photoContainerView release]; 

    photoContainerView = nil; 

    if(rotPhoto1 != nil){ 
    [rotPhoto1 release]; 
     rotPhoto1 = nil; 
    } 
    if(rotPhoto2 != nil){ 
    [rotPhoto2 release]; 
    rotPhoto2 = nil; 
    } 
    if(rotPhoto3 != nil){ 
    [rotPhoto3 release]; 
    rotPhoto3 = nil; 
    } 
    if(rotPhoto4 != nil){ 
    [rotPhoto4 release]; 
    rotPhoto4 = nil; 
    } 

    if(rotPhotoSm1 != nil){ 
    [rotPhotoSm1 release]; 
    rotPhotoSm1 = nil; 
    } 
    if(rotPhotoSm2 != nil){ 
    [rotPhotoSm2 release]; 
    rotPhotoSm2 = nil; 
    } 
    if(rotPhotoSm3 != nil){ 
    [rotPhotoSm3 release]; 
    rotPhotoSm3 = nil; 
    } 
    if(rotPhotoSm4 != nil){ 
    [rotPhotoSm4 release]; 
    rotPhotoSm4 = nil; 
    } 

    return photoFlattened; 

} 



//-----------------------------STOP THE UIACTIVITYINDICATORVIEW--------------------- 



-(IBAction)stopSpinner:(id)sender 
{ 

    [self.view setUserInteractionEnabled:YES]; 
    [myIndicator stopAnimating]; 
    [myIndicator release]; 
    myIndicator = nil; 

    if(myPattern == 0){ 
     NSLog(@"SINGLE-SHOT MODE"); 
     controller.isSingleShot = TRUE; 

    }else{ 

     NSLog(@"MULTI-SHOT MODE"); 
     controller.isSingleShot = FALSE; 

    } 

    controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; 
    [self presentModalViewController:controller animated:YES]; 

    [controller release]; 

    [allStamps removeAllObjects]; 
    [imageFrames removeAllObjects]; 


    switch (myShots) { 
     case 1: 
      [editPhotoData1 release]; 
      break; 

     case 2: 
      [editPhotoData1 release]; 
      [editPhotoData2 release]; 
      break; 

     case 4: 
      [editPhotoData1 release]; 
      [editPhotoData2 release]; 
      [editPhotoData3 release]; 
      [editPhotoData4 release]; 
      break; 

    } 

     /* This is the edited photo that has been onscreen. Processing is now done so it is okay to release it. The UI should be updated and now have a blank, black background instead of the image. 
*/ 
     editedPhoto.image = nil; 
    [editedPhoto release]; 
    editedPhoto = nil; 


} 
+0

Potrebbe essere utile aggiungere il metodo che esegue il thread. –

+0

Ok, grazie lo farò. – RexOnRoids

risposta

15

Questa domanda ha alcune buone risorse sul cacao multithreading: "Where can I find a good tutorial on iPhone/Objective c multithreading?"

Ho anche consigliare vivamente la lettura del nuovo Concurrency Programming Guide ( tuttavia, ignorare i blocchi e code di spedizione, come Grand Central Dispatch non è ancora disponibile su iPhone OS iOS 4.0 ha appena aggiunto blocchi e GCD), perché rende particolarmente utile l'utilizzo di strutture come NSOperation e NSOperationQueue come alternativa ai thread creati manualmente. Per informazioni sui thread creati manualmente, consultare Threading Programming Guide.

Come RC cita, l'unica fonte principale di arresti anomali con applicazioni Cocoa multithread è l'accesso simultaneo a una risorsa condivisa. La direttiva @synchronized non è la più veloce, come pointed out by Colin Wheeler, quindi è possibile utilizzare NSLock per proteggere l'accesso alle risorse condivise. Tuttavia, il blocco di qualsiasi tipo può essere costoso, motivo per cui sto migrando le mie applicazioni fino a utilizzare NSOperationQueues a larghezza singola per l'accesso a queste risorse. I miglioramenti delle prestazioni sono stati significativi.

Un'altra area problematica con Cocoa e il multithreading deriva dagli aggiornamenti dell'interfaccia utente. Tutti gli aggiornamenti dell'interfaccia utente in Cocoa devono essere eseguiti sul thread principale, oppure può verificarsi un'instabilità. Se hai un thread in background che esegue un calcolo, assicurati di avvolgere qualsiasi metodo aggiorni l'interfaccia utente in una chiamata al metodo -performSelectorOnMainThread:withObject:waitUntilDone:.

+0

Molto bene ... Sono particolarmente interessato alla parte in cui si menziona che gli aggiornamenti dell'interfaccia utente possono causare problemi con il multithreading. Perché nella mia app invio una porzione significativa di lavoro (incluso alcuni relativi all'interfaccia utente) a un thread in background in modo che possa mostrare una vista UIActivityIndicator. A volte si blocca, a volte no - in condizioni COSTANTE, attenzione. Questo porta a chiedersi se l'instabilità risiede nel modo in cui l'app si riferisce ai componenti del sistema operativo dell'iPhone stesso al momento dello schianto a causa dell'impatto del thread aggiuntivo. Dovrò esaminarlo di più. Grazie. – RexOnRoids

+0

I problemi di threading spesso causano arresti non deterministici. Questo è ciò che li rende così divertenti. Nel tuo codice precedente ho notato che esegui un rendering di un livello in un contesto all'interno di -buildPhoto che viene eseguito in background. Non sono sicuro che sia un'operazione sicura. –

+0

Grazie! Lo esaminerò. – RexOnRoids

5

Probabilmente i principianti più errore comune (in qualsiasi lingua) quando si lavora con fili sta permettendo l'accesso alle risorse condivise mutabili senza protezioni/mutex. È guardia risorse come:

 
@synchronized(sharedData) 
{ 
    // modify sharedData safely 
} 

ti consigliamo di limitare la quantità di dati condivisi tra i thread e se deve essere condivisa, preferisce oggetti immutabili al fine di ridurre i conflitti causati da sincronizzazione.

La gestione dei thread è un altro luogo in cui possono sorgere problemi. Ecco un riferimento al documento specifico per l'uso dei thread nell'iPhone.

http://developer.apple.com/iphone/library/documentation/cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html.

Senza fornire codice, nessuno può immaginare cosa c'è di sbagliato nella tua app, ma vorrei iniziare assicurandoti di gestire correttamente la creazione e la chiusura del thread e ponendo particolare attenzione alle risorse condivise che thread tentativi di accesso.

+0

Fresco. RC menziona un'altra cosa buona: distinzione tra creazione e cessazione.Io uso metodi come -performSelectorInBackground: withObject per creare un thread, ma non so cosa sto facendo per terminarlo come avevo presunto che alla fine del lavoro il thread si sarebbe concluso. Dovrò leggere di più la documentazione. Grazie RC. – RexOnRoids

+0

Il thread dovrebbe terminare quando il metodo raggiunge la fine. Non è necessario distruggerlo manualmente. –

Problemi correlati