How to wait for an asynchronous method to be over?

2019-07-28 05:07发布

问题:

I have a toolkit that I need to work with (to interface with a remote service). This toolkit queries the remote service and asks for results. It does this asynchronously, which in most cases is good, but not for creating concise methods. I want to make methods similar to the following:

-(NSArray *)getAllAccounts {
    NSString *query = @"SELECT name FROM Account";
    //Sets "result" to the query response if no errors.
    //queryResult:error:context: is called when the data is received
    [myToolkit query:query target:self selector:@selector(queryResult:error:context:) context:nil];

    //Wait?

    return result.records;
}

The problem is, inside the toolkit the methods call each other using @selector, not direct calls, so getting return values is difficult. Further, the actual query uses:

NSURLConnection *connection = [[[NSURLConnection alloc] initWithRequest:aRequest delegate:self] autorelease];

Which is asynchronous. By the time the data has been received from the service, my method has long ago returned... without the information. So my question is this: Is there a way to pause execution until the data has been returned? Could I accomplish this using a second thread to get the data while the main thread rests (or using 3 threads so the main thread doesn't rest?)

I don't want to edit the toolkit to change their method (or add a new one) to be synchronous, so is there a way to make a method as I want?

回答1:

You might want to consider NOT making it all synchronous, especially if the sample code in your post is run on your main application thread. If you do that, the main thread will block the UI and the application will cease to respond until the remote transaction is complete.

Therefore, if you really insist on the synchronous approach, then you should definitely do it in a background thread so that the UI does not become unresponsive, which can actually lead to your App getting killed by the OS on iphone.

To do the work in a background thread, I would strongly recommend using the Grand Central Dispatch stuff, namely NSBlockOperation. It will free you from having to actually create and manage threads and makes your code pretty neat.

To do the synchronous thing, take a look at the NSCondition class documentation. You could do something like the following:

NSCondition* condition = ...;
bool finished = NO;

-(NSArray *)getAllAccounts {
    [condition lock];
    NSString *query = @"SELECT name FROM Account";
    //Sets "result" to the query response if no errors.
    //queryResult:error:context: is called when the data is received
    [myToolkit query:query target:self selector:@selector(queryResult:error:context:) context:nil];

    while (!finished)
        [condition wait]; 

    [condition unlock];
    return result.records;
}

Then in the method called by the toolkit to provide the results you'd do:

- (void) queryResult:error:context: {
    //  Deal with results
    [condition lock]
    finished = YES;
    [condition signal];
    [condition unlock];
}

You'd probably want to encapsulate the "condition" and "finished" variables in your class declaration.

Hope this helps.

UPDATE: Here is some code to offload the work to a background thread:

NSOperationQueue* queue = [NSOperationQueue new];
[queue addOperationWithBlock:^{
    //  Invoke getAllAccounts method
}];

Of course, you can keep the queue around for later use and move the actual queuing of the work to inside your method call to make things neater.



回答2:

The way to wait is to return from your current code. Finish up doing what you want done after the wait, in the asynchronous callback method you specify. What's so difficult about that?

Any synchronous waits in the main UI thread will block the UI and make the user think your app has locked up, which is likely far worse than your thinking the code isn't concise enough.