Bindings Cocoa and new Thread (Spinning Beach Ball an new Thread)

  • I have an application which refreshes a NSTable with data from a
    network server. The refresh can take several seconds, and might even
    fail when the server is not accessible. During the refresh process I
    would like to display a sheet with a spinning progress indicator.

    Everything worked sort of OK until I added some threading. The reason
    I wanted to add a new thread was to free up the main thread to prevent
    the spinning beach ball from appearing. The beach ball appeared every-
    time when then process took a little bit longer then it should.

    On my main form I have a button which is link to the refresh action:

    - (IBAction) refresh:(id)sender
    {
    [NSThread detachNewThreadSelector:@selector(processRefreshGroup:)
    toTarget:self withObject:sender];
    //[self processRefreshGroup:sender];
    }

    - (void) processRefreshGroup:(id)sender
    {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSApplication * application = [NSApplication sharedApplication];
    [application beginSheet:reloadGroupsPanel modalForWindow:[application
    keyWindow] modalDelegate:nil didEndSelector:nil contextInfo:nil];
    [application endSheet:reloadGroupsPanel];
    @try
    {
      [reloadGroupsProgressIndicator startAnimation:sender];
      [groups removeAllObjects];

      //This method can take a long time to complete
      [self doExpensiveWorkHere];

      [reloadGroupsProgressIndicator stopAnimation:self];
      [application stopModal];
      [reloadGroupsPanel close];
    }
    @catch (NSException * exception)
    {
      [reloadGroupsProgressIndicator stopAnimation:self];
      [application stopModal];
      [reloadGroupsPanel close];

      [errorMessageField setString:[exception reason]];
      NSApplication * application = [NSApplication sharedApplication];
      [application beginSheet:errorPanel modalForWindow:[application
    keyWindow] modalDelegate:nil didEndSelector:nil contextInfo:nil];
      [application endSheet:errorPanel];
    }
    //Reload data on NSTable
    [groupSelectionTable reloadData];
    [pool release];
    }

    I have a couple of questions:

    1.) I gathered i have to create a new NSAutoReleasePool in my
    "threaded" method. Is this correct ?

    2.) During execution of this I am updating the UI components from a
    thread which is not the main thread. This produces several error
    messages. How can I updated UI components. I tried using
    performSelectorOnMainThread, but this didn't work either. Not sure if
    this is the right approach.

    3.) Is there an alternative way to achieve this without the beach ball
    appearing. I understand what caused it was the fact that I never
    exited the action method, at least not quickly. I would prefer an
    approach which did not require a new thread to be created.

    Thanks for you help
    Alex
  • Hi Alex,

    On Thu, Jun 5, 2008 at 12:10 AM, Alexander Hartner <alex...> wrote:

    > 1.) I gathered i have to create a new NSAutoReleasePool in my "threaded"
    > method. Is this correct ?

    Yes.

    > 2.) During execution of this I am updating the UI components from a thread
    > which is not the main thread. This produces several error messages. How can
    > I updated UI components. I tried using performSelectorOnMainThread, but this
    > didn't work either. Not sure if this is the right approach.

    See below.

    > 3.) Is there an alternative way to achieve this without the beach ball
    > appearing. I understand what caused it was the fact that I never exited the
    > action method, at least not quickly. I would prefer an approach which did
    > not require a new thread to be created.

    Not really, no.

    Here is some code I wrote a long time ago for doing something similar.
    I had a document-based app that loaded MP3 files. For my app's
    purposes, they had to be decoded to PCM data before they were useful,
    so while that was happening I dropped a document-modal sheet with a
    progress bar and a cancel button.

    - (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName
    error:(NSError **)outError
    {
    if ([typeName isEqualToString:@"MP3 Audio File"])
    {
      myFileData = [data retain];
      [self performSelector:@selector(startDecoding:) withObject:typeName
    afterDelay:0];
      [self performSelector:@selector(decodeMP3Data:) withObject:data afterDelay:0];
      return YES;
    }
    else
      return [super readFromData:data ofType:typeName error:outError];
    }

    - (void)startDecoding:(NSString *)typeName
    {
    myCancelProgressFlag = FALSE;
    [progressSheetInfo setStringValue:
      [@"Decoding " stringByAppendingString: (NSString *)typeName]];
    [progressSheetBar setUsesThreadedAnimation:YES];
    [progressSheetBar setIndeterminate:YES];
    [progressSheetBar startAnimation:self];
    [NSApp beginSheet:progressSheet
              modalForWindow:[[[self windowControllers] objectAtIndex:0] window]
              modalDelegate:nil
              didEndSelector:nil
              contextInfo:nil];
    }

    - (void)endDecoding:(id)userData
    {
    [progressSheetBar setIndeterminate:NO];
    [progressSheetBar stopAnimation:self];
    [progressSheet orderOut:self];
    [NSApp endSheet:progressSheet];
    }

    - (void)decodeMP3Data:(id)data
    {
    [NSThread detachNewThreadSelector: @selector(decodeMP3DataThread:)
      toTarget:self withObject:data];
    }

    - (void)decodeMP3DataThread:(id)data
    {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    myPCMData = [[AudioDecoder decodeMP3Data:data progressObject:self] retain];
    [self performSelectorOnMainThread: myCancelProgressFlag ?
      @selector(close) : @selector(endDecoding:)
      withObject:nil waitUntilDone:NO];

    [pool release];
    }

    - (BOOL)progressSheetCheck:(float)frac
    {
    if (myCancelProgressFlag)
      return FALSE;

    if ([progressSheetBar isIndeterminate])
    {
      [progressSheetBar setIndeterminate:NO];
      [progressSheetBar stopAnimation:self];
    }
    [progressSheetBar setDoubleValue:(double)frac];

    return TRUE;
    }

    The main loop of the decoder looked something like this:

    while (moreToDo)
    {
    float progress = 0.0;
    if ([progressObject progressSheetCheck:progress])
    {
      // do a small amount more
      progress += small_amount;
    }
    else
      break;
    }

    Hope this helps,
    Hamish
  • On Jun 4, 2008, at 5:10 PM, Alexander Hartner wrote:

    > I have an application which refreshes a NSTable with data from a
    > network server. The refresh can take several seconds, and might even
    > fail when the server is not accessible. During the refresh process I
    > would like to display a sheet with a spinning progress indicator.
    >
    > Everything worked sort of OK until I added some threading. The
    > reason I wanted to add a new thread was to free up the main thread
    > to prevent the spinning beach ball from appearing. The beach ball
    > appeared every-time when then process took a little bit longer then
    > it should.
    >
    > On my main form I have a button which is link to the refresh action:
    >
    > - (IBAction) refresh:(id)sender
    > {
    > [NSThread detachNewThreadSelector:@selector(processRefreshGroup:)
    > toTarget:self withObject:sender];
    > //[self processRefreshGroup:sender];
    > }
    >
    > - (void) processRefreshGroup:(id)sender
    > {
    > NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    > NSApplication * application = [NSApplication sharedApplication];
    > [application beginSheet:reloadGroupsPanel modalForWindow:
    > [application keyWindow] modalDelegate:nil didEndSelector:nil
    > contextInfo:nil];
    > [application endSheet:reloadGroupsPanel];
    > @try
    > {
    > [reloadGroupsProgressIndicator startAnimation:sender];
    > [groups removeAllObjects];
    >
    > //This method can take a long time to complete
    > [self doExpensiveWorkHere];
    >
    > [reloadGroupsProgressIndicator stopAnimation:self];
    > [application stopModal];
    > [reloadGroupsPanel close];
    > }
    > @catch (NSException * exception)
    > {
    > [reloadGroupsProgressIndicator stopAnimation:self];
    > [application stopModal];
    > [reloadGroupsPanel close];
    >
    > [errorMessageField setString:[exception reason]];
    > NSApplication * application = [NSApplication sharedApplication];
    > [application beginSheet:errorPanel modalForWindow:[application
    > keyWindow] modalDelegate:nil didEndSelector:nil
    > contextInfo:nil];
    > [application endSheet:errorPanel];
    > }
    > //Reload data on NSTable
    > [groupSelectionTable reloadData];
    > [pool release];
    > }
    >
    > I have a couple of questions:
    >
    > 1.) I gathered i have to create a new NSAutoReleasePool in my
    > "threaded" method. Is this correct ?

    Yes.

    > 2.) During execution of this I am updating the UI components from a
    > thread which is not the main thread.

    Ruh-roh!

    > This produces several error messages. How can I updated UI components.

    performSelectorOnMainThread

    > I tried using performSelectorOnMainThread,

    There you go!

    > but this didn't work either. Not sure if this is the right approach.

    Define "didn't work".  Maybe show us the code you used; I'm pretty
    this is the right way to do it.

    > 3.) Is there an alternative way to achieve this without the beach
    > ball appearing. I understand what caused it was the fact that I
    > never exited the action method, at least not quickly. I would prefer
    > an approach which did not require a new thread to be created.

    You're on the right track; you must have had some other problem with
    your performSelectorOnMainThread solution.
  • On Jun 4, 2008, at 4:10 PM, Alexander Hartner wrote:
    > 1.) I gathered i have to create a new NSAutoReleasePool in my
    > "threaded" method. Is this correct ?

    Yes, every thread needs a separate AutoReleasePool. I don't know from
    the top of my head if NSThread creates one for you (I usually use
    pthreads and there you have to create your own).

    > 2.) During execution of this I am updating the UI components from a
    > thread which is not the main thread. This produces several error
    > messages. How can I updated UI components. I tried using
    > performSelectorOnMainThread, but this didn't work either. Not sure
    > if this is the right approach.

    Basic rule of threading on OS X: don't do any UI stuff whatsoever
    outside of the main thread.

    > 3.) Is there an alternative way to achieve this without the beach
    > ball appearing. I understand what caused it was the fact that I
    > never exited the action method, at least not quickly. I would prefer
    > an approach which did not require a new thread to be created.

    Are you using blocking sockets to talk to the remote server? If so,
    using non-blocking sockets can help you do the entire communication
    based on events instead of polling.

    -Stefan
  • Thanks to all those who responded. I managed to implement what you
    suggested. Previously I did had some issues, but managed to resolve
    them. One mistake I made before was doing the main work in the main
    thread via performSelectorOnMainThread. I now separated the UI updated
    and the long task into different methods.

    Thanks again
    Alex

    On 5 Jun 2008, at 00:10, Alexander Hartner wrote:

    > I have an application which refreshes a NSTable with data from a
    > network server. The refresh can take several seconds, and might even
    > fail when the server is not accessible. During the refresh process I
    > would like to display a sheet with a spinning progress indicator.
    >
    > Everything worked sort of OK until I added some threading. The
    > reason I wanted to add a new thread was to free up the main thread
    > to prevent the spinning beach ball from appearing. The beach ball
    > appeared every-time when then process took a little bit longer then
    > it should.
    >
    > On my main form I have a button which is link to the refresh action:
    >
    > - (IBAction) refresh:(id)sender
    > {
    > [NSThread detachNewThreadSelector:@selector(processRefreshGroup:)
    > toTarget:self withObject:sender];
    > //[self processRefreshGroup:sender];
    > }
    >
    > - (void) processRefreshGroup:(id)sender
    > {
    > NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    > NSApplication * application = [NSApplication sharedApplication];
    > [application beginSheet:reloadGroupsPanel modalForWindow:
    > [application keyWindow] modalDelegate:nil didEndSelector:nil
    > contextInfo:nil];
    > [application endSheet:reloadGroupsPanel];
    > @try
    > {
    > [reloadGroupsProgressIndicator startAnimation:sender];
    > [groups removeAllObjects];
    >
    > //This method can take a long time to complete
    > [self doExpensiveWorkHere];
    >
    > [reloadGroupsProgressIndicator stopAnimation:self];
    > [application stopModal];
    > [reloadGroupsPanel close];
    > }
    > @catch (NSException * exception)
    > {
    > [reloadGroupsProgressIndicator stopAnimation:self];
    > [application stopModal];
    > [reloadGroupsPanel close];
    >
    > [errorMessageField setString:[exception reason]];
    > NSApplication * application = [NSApplication sharedApplication];
    > [application beginSheet:errorPanel modalForWindow:[application
    > keyWindow] modalDelegate:nil didEndSelector:nil
    > contextInfo:nil];
    > [application endSheet:errorPanel];
    > }
    > //Reload data on NSTable
    > [groupSelectionTable reloadData];
    > [pool release];
    > }
    >
    > I have a couple of questions:
    >
    > 1.) I gathered i have to create a new NSAutoReleasePool in my
    > "threaded" method. Is this correct ?
    >
    > 2.) During execution of this I am updating the UI components from a
    > thread which is not the main thread. This produces several error
    > messages. How can I updated UI components. I tried using
    > performSelectorOnMainThread, but this didn't work either. Not sure
    > if this is the right approach.
    >
    > 3.) Is there an alternative way to achieve this without the beach
    > ball appearing. I understand what caused it was the fact that I
    > never exited the action method, at least not quickly. I would prefer
    > an approach which did not require a new thread to be created.
    >
    > Thanks for you help
    > Alex
previous month june 2008 next month
MTWTFSS
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30            
Go to today