2009-08-31 10 views
8

Właśnie wprowadziłem wielowątkowość do mojej aplikacji, TYLKO po to, aby uruchomić głupi interfejs UIActivityIndicatorView. Cóż, wskaźnik aktywności działa, w porządku - ale teraz moja aplikacja czasami się zawiesza, a czasami nie - w innych kontrolowanych warunkach ... Muszę to zrozumieć, ale nie wiem, od czego zacząć ...Wspólne błędy wielowątkowości dla początkujących na iPhone'ie

Więc, jakie są typowe błędy, które początkujący często popełniają z wielowątkowością na iPhone'ie? Proszę dokładnie opisać swoje odpowiedzi. Dziękuję za Twój czas.

UPDATE: Dodałem swoje problematyczne źródło w celach informacyjnych.

//--------------------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

Pomocne może być dodanie metody wykonywania wątku. –

+0

OK, dziękuję, zrobię to. – RexOnRoids

Odpowiedz

15

To pytanie ma kilka dobrych zasobów kakao wielowątkowości: "Where can I find a good tutorial on iPhone/Objective c multithreading?"

Ja również bardzo polecam czytanie nowej Concurrency Programming Guide ( jednak ignorować bloki i kolejki wysyłkowe, jak Grand Central Dispatch nie jest jeszcze dostępna na iPhone OS iOS 4.0 właśnie dodał bloki i GCD), ponieważ stanowi mocne argumenty za używaniem struktur takich jak NSOperation i NSOperationQueue jako alternatywa dla ręcznie tworzonych wątków. Informacje na temat ręcznie utworzonych wątków można znaleźć w artykule Threading Programming Guide.

Jak wspomina RC, największym pojedynczym źródłem awarii przy wielowątkowych aplikacjach Cocoa jest równoczesny dostęp do współużytkowanego zasobu. Dyrektywa @synchronized nie jest najszybsza, jako pointed out by Colin Wheeler, więc możesz chcieć użyć NSLock, aby chronić dostęp do swoich udostępnionych zasobów. Jednak blokowanie dowolnego rodzaju może być kosztowne, dlatego też migrowałem swoje aplikacje, aby uzyskać dostęp do tych zasobów w jednym szerokim zakresie: NSOperationQueues. Poprawa wydajności była znaczna.

Kolejny obszar problemowy związany z kakao i wielowątkowością pochodzi z aktualizacji interfejsu użytkownika. Wszystkie uaktualnienia interfejsu użytkownika w kakao muszą być wykonywane w głównym wątku, co może spowodować niestabilność. Jeśli masz wątek w tle wykonujący obliczenia, upewnij się, że zawijasz dowolną metodę aktualizującą interfejs w wywołaniu metody -performSelectorOnMainThread:withObject:waitUntilDone:.

+0

Bardzo dobrze ... Szczególnie interesuje mnie część, w której wspomniałeś, że aktualizacje interfejsu użytkownika mogą powodować problemy z wielowątkowością. Ponieważ w mojej aplikacji wysyłam znaczną część pracy (w tym niektóre związane z interfejsem użytkownika) do wątku w tle, dzięki czemu mogę wyświetlać widok UIActivityIndicator. Czasami się zawiesza, czasami nie - w STAŁYCH warunkach, pamiętajcie. Prowadzi to do zastanowienia się, czy niestabilność leży w sposobie, w jaki aplikacja odnosi się do komponentów samego iPhone'a w momencie awarii z powodu wpływu dodatkowego wątku. Będę musiał zagłębić się w to więcej. Dzięki. – RexOnRoids

+0

Problemy z wątkami często powodują niedeterministyczne awarie. To sprawia, że ​​są tak zabawni. Zauważyłem w powyższym kodzie, że wykonujesz renderowanie warstwy w kontekście wewnątrz -buildPhoto, który działa w tle. Nie jestem pewien, czy to operacja wątków. –

+0

Dzięki! Zajrzę w to. – RexOnRoids

5

Prawdopodobnie początkujących Najczęstszym błędem zrobić (w dowolnym języku) podczas pracy z wątków jest umożliwienie dostępu do zasobów udostępnionych modyfikowalnych bez osłon/muteksy. Strzec zasoby, takie jak:

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

będziemy chcieli ograniczyć ilość danych dzielonych pomiędzy wątkami i jeśli to musi być wspólna, wolą niezmienne obiektów w celu zmniejszenia twierdzenie spowodowanego synchronizacji.

Zarządzanie wątkami to kolejne miejsce, w którym mogą pojawić się problemy. Oto odniesienie do dokumentu dotyczące używania wątków w telefonie iPhone.

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

Bez podania kodu, nikt nie zgadnie, co jest nie tak z aplikacją, ale zacznę od upewnienia się, że prawidłowo zarządzasz tworzeniem i zakończeniem wątku, a także zwracasz szczególną uwagę na wszystkie udostępnione zasoby, które próby dostępu.

+0

Cool. RC wspomina o kolejnej dobrej rzeczy: Rozróżnienie między tworzeniem a zakończeniem.Używam metod takich jak -performSelectorInBackground: withObject do utworzenia wątku, ale nie wiem, co robię, aby go zakończyć, ponieważ założyłem, że pod koniec pracy wątek po prostu się kończy. Będę musiał przeczytać więcej dokumentacji. Dzięki RC. – RexOnRoids

+0

Wątek powinien zakończyć się, gdy metoda osiągnie koniec. Nie musisz ręcznie niszczyć go. –

Powiązane problemy