Grand Central Dispatch and functions

自古美人都是妖i 提交于 2019-12-25 02:43:35

问题


I've been looking at this question to try to solve the problem I have here. The tl;dr is I want to use GCD to let me show a "Waiting" screen while I preform some tasks, then hide the screen when it's done. Right now, I have

- (void) doStuff
{
    // Show wait on start
    [self.waitScreen setHidden:NO];

    dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
    dispatch_async(queue, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            // Double nesting the dispatches seems to allow me to do UI changes as part of 'Code to execute' below.
            // If I do not double nest like this, the UI still freezes while it executes
            dispatch_queue_t queue2 = dispatch_queue_create("com.myDomain.myApp",null);
            dispatch_async(queue2, ^{
                dispatch_async(dispatch_get_main_queue(), ^{

                    // Code to execute
                    {
                        //... Do my time consuming stuff here ...
                        // For testing purposes, I'm using
                        int i = 0;
                        while (i < 1000000000)
                        {
                            i++;
                        }
                    }

                    // Hide Wait Screen on End
                    [self.waitScreen setHidden:YES];
                });
            });
        });
    });
}

And this works just how I want. I'm calling [self doStuff] like so

- (IBAction) buttonTouchUpInside:(id)sender
{
    [self doStuff];
}

- (void) doStuff
{
    // ... code from first code block here ...
}

Everything to this point works perfectly. Now, I've discovered I will need to use this in a function call. So I need something like:

- (IBAction) buttonTouchUpInside:(id)sender
{
    NSMutableString *string= [self doStuff];

    // ... use 'string' to do other stuff ...
    // For testing, I'm using
    self.label.text = string;
}

- (NSMutableString *) doStuff
{
    // ... code from first code block here ...
}

How do I need to change the syntax to be able to pass variables around with dispatch_async?


I looked at the Apple Docs and tried

- (IBAction) buttonTouchUpInside:(id)sender
{
    NSMutableString *string= [self doStuff];

    // ... use 'string' to do other stuff - shows 'nil' when I put breakpoints here ...
    // For testing, I'm using
    self.label.text = string;
}

- (NSMutableString *) doStuff
{
    __block NSMutableString *string = [[NSMutableString alloc] initWithString:@"Initing"];

    // Show wait on start
    [self.waitScreen setHidden:NO];

    dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
    dispatch_async(queue, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
            dispatch_async(queue, ^{
                dispatch_async(dispatch_get_main_queue(), ^{

                    // Code to execute
                    {
                        int i = 0;
                        while (i < 1000000000)
                        {
                            i++;
                        }

                        [string setString:@"Hello World"];
                    }

                    // Hide Wait Screen on End
                    [self.waitScreen setHidden:YES];
                });
            });
        });
    });

    return string;
}

But when I run this, label just shows Initing. I need it to show Hello World. None of the changes I make in the block are being passed through.

After looking at some other questions, this seems to be referred to as a "race condition". As I understand it, once it hits the dispatch_async, the code in the block starts running on a new thread, but the code outside of the block continues to run at the same time on the old thread. So it looks like the thread outside the block is getting to self.label.text = string before the thread running the block can get to [string setString:@"Hello World"];. How can I make the self.label.text = string line wait until [string setString:@"Hello World"]; finishes?


回答1:


First of all your reasoning of double nesting is flawed. Not sure why it might have worked, but the correct way is to do some async work, and any time you want to update the ui wrap that code in a block on the main queue.

- (void) doStuff
{
    // Show wait on start
    [self.waitScreen setHidden:NO];

    // queue should be a global variable, you don't want to create it every time you
    // execute doStuff
    dispatch_async(queue, ^{
        // Code to execute
        {
            //... Do my time consuming stuff here ...
            // For testing purposes, I'm using
            int i = 0;
            while (i < 1000000000)
            {
                i++;
            }
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            // Hide Wait Screen on End
            [self.waitScreen setHidden:YES];
        });
    });
}

Since your queue is performing work asynchronously you can't simply return a value from doStuff without waiting, which will block the queue you call doStuff on again.

If you just want to set a value on a label, do that too in the block executed on the main queue, like hiding the wait screen.

Another common way to do things it to provide a callback block to execute as soon as work is finished.

- (void) doStuffWithCompletionBlock:(void(^)(NSString *))block
{
    // again, a global variable for the queue
    dispatch_async(queue, ^{
        // do some work here that shouldn't block the UI

        dispatch_async(dispatch_get_main_queue(), ^{
            block(@"My result string");
        });
    });
}

- (void) myAction:(id)sender
{
    __weak typeof(self) weakSelf = self;
    [self doStuffWithCompletionBlock:^(NSString *result) {
        weakSelf.label.text = result;
    }];
}

Notice that I call the completion block on the main queue, this is a choice. You could leave that out, but then you would still have do all UI updates on the main queue later in the completion block itself.



来源:https://stackoverflow.com/questions/21465757/grand-central-dispatch-and-functions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!