cocoa-touchbonjoursynchronousnsrunloopnsnetservice

Synchronous Bonjour resolution with NSNetService (why is it taking so long to return control from the runloop?)


I am trying to do a Bonjour resolution of a service name synchronously, so I am trying to figure out how to properly wait for the netServiceDidResolveAddress() call of NSNetService.

My code looks like this:

service = [[NSNetService alloc] initWithDomain:@"local." type:@"_myservice._tcp" name:[self name]];
[service retain];
[service setDelegate:self];
waitingOnResolution = true;
[service resolveWithTimeout:5.0];

for (int i=0;i<5;i++) {
    DebugLog(@"resolve: resolve iteraton %u\n", i);
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; // run loop for one second
    DebugLog(@"resolve: resolve iteraton %u (after run loop)\n", i);
    if (!waitingOnResolution) break;   
}

// continue processing...

And here is my function that handles the resolution success:

- (void)netServiceDidResolveAddress:(NSNetService *)sender
{
    // do processing...
    waitingOnResolution = false;
    CFRunLoopStop([[NSRunLoop currentRunLoop] getCFRunLoop]); // has no effect
}

As long as my code continues to run a second or so after the resolve event comes in, I'll be happy.

What is actually happening is the resolve happens pretty quickly, and I see the code in netServiceDidResolveAddress() complete, but then there is consistently an undesirable 5-6 second wait until control returns from runUntilDate and I see the "...(after run loop) debug statement.

I looked around on several message boards and saw similar questions, but their solutions (like using CFRunLoopStop) don't seem to have any effect.

Here is an example of logging I receive:

2011-07-11 09:38:07.267 MyProgram[1822:707] resolve: resolve iteraton 0
2011-07-11 09:38:07.299 MyProgram[1822:707] netService: netServiceDidResolveAddress function ended

[5+ second delay]

2011-07-11 09:38:12.907 MyProgram[1822:707] resolve: resolve iteraton 0 (after run loop)

If someone can tell me of a different way to use NSNetService resolution to get a synchronous result, or refactor my runloop-related code to avoid this (seemingly OS-created) 5-6 second delay, I would appreciate it.


Solution

  • You really shouldn't be doing this. Network resolution is asynchronous for a good reason: it might take a long time to perform the resolution, and in most cases, you don't want to block your thread for the duration. If you are doing the resolution on the main thread, you really do not want to do it synchronously. On Mac OS X, a long-running operation blocking the main thread will result in a spinning beachball. On iOS, the watchdog might kill your app entirely.

    The API is asynchronous to encourage you to use it properly. Don't try to fight it.

    Since you don't explain why you're trying to do this synchronously, I can't help much more, but keep in mind that Cocoa is event driven. Break your synchronous steps into states and let the callbacks from Cocoa during each stage drive your state machine forward. It won't look as nice as a line-by-line procedural function, but it will keep your UI responsive and avoid ugly workarounds.