本文介绍了以下是 dispatch_set_target_queue() 的安全使用吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想要做的是创建一个间接的队列定位 主队列.

What I want to do is create an indirect queue targeting the main queue.

dispatch_queue_t myQueue = dispatch_queue_create("com.mydomain.my-main-queue", NULL);
dispatch_set_target_queue(myQueue, dispatch_get_main_queue());

我的最终目标是将队列用作 underlyingQueue NSOperationQueue 的属性,因为 Apple 的文档明确声明不要使用 dispatch_get_main_queue().虽然使用间接队列,但它在技术上遵循文档.

My ultimate goal is to use the queue as the underlyingQueue property of an NSOperationQueue, because Apple's documentation clearly states not to use dispatch_get_main_queue(). Though using an indirect queue it technically is following the documentation.

这一切的原因是因为NSOperationQueue.mainQueue 对于异步操作来说不是安全的,因为它是全局可访问的,并且它是 maxConcurrentOperationCount 设置为 1.所以可以很容易地用这个操作队列来拍自己的脚.

The reason for all this is because NSOperationQueue.mainQueue is not a safe for asynchronous operations, because it is globally accessible and it's maxConcurrentOperationCount is set to 1. So can easily shoot yourself in the foot with this operation queue.

更新 1

对于这个问题假设异步 NSOperation"是什么的基础,似乎有很多困惑.需要明确的是,这是基于此 WWDC session 中的概念概念是使用操作准备"和依赖管理来管理应用程序中的任务,这意味着将异步 NSOperations 添加到 NSOperationQueues 以利用这一点.如果您将这些概念理解为这个问题的精神,希望推理会更有意义,并且您可以专注于将解决方案与其他解决方案进行比较和对比.

It seems there is a lot of confusion about the basis of what this question assumes an "asynchronous NSOperation" is. To be clear this is based on the concepts in this WWDC session The particular concept is using "operation readiness" and dependency management to manage the tasks in your app, which means asynchronous NSOperations are added to NSOperationQueues to take advantage of this. If you take these concepts to the spirit of this question hopefully the reasoning will make more sense, and you can focus on comparing and contrasting the solution with other ones.

更新 2 - 问题示例:

// VendorManager represents any class that you are not in direct control over.

@interface VendorManager : NSObject
@end

@implementation VendorManager

+ (void)doAnsyncVendorRoutine:(void (^)(void))completion {
    // Need to do some expensive work, make sure we are off the main thread
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND 0), ^(void) {
        // Some off main thread background work
        sleep(10);
        // We are done, go back to main thread
        [NSOperationQueue.mainQueue addOperationWithBlock:completion];
    });
}

@end


// MYAsyncBoilerPlateOperation represents all the boilerplate needed
// to implement a useful asnychronous NSOperation implementation.

@interface MYAlertOperation : MYAsyncBoilerPlateOperation
@end

@implementation MYAlertOperation

- (void)main {

    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:"Vendor"
                                                                             message:"Should vendor do work?"
                                                                      preferredStyle:UIAlertControllerStyleAlert];
    __weak __typeof(self) weakSelf = self;
    [alertController addAction:[UIAlertAction actionWithTitle:@"Yes"
                                                        style:UIAlertActionStyleDefault
                                                      handler:^(UIAlertAction *action) {
                                                          [VendorManager doAnsyncVendorRoutine:^{
                                                              // implemented in MYAsyncBoilerPlateOperation
                                                              [weakSelf completeThisOperation];
                                                          }];
                                                      }]];
    [alertController addAction:[UIAlertAction actionWithTitle:@"No"
                                                        style:UIAlertActionStyleDefault
                                                      handler:^(UIAlertAction *action) {
                                                          [weakSelf cancel];
                                                      }]];

    [MYAlertManager sharedInstance] presentAlert:alertController animated:YES];
}

@end

// MYAlertOperation will never complete.
// Because of an indirect dependency on operations being run on mainQueue.
// This example is an issue because mainQueue maxConcurrentOperationCount is 1.
// This example would not be an issue if maxConcurrentOperationCount was > 1.

[NSOperationQueue.mainQueue addOperation:[[MYAlertOperation alloc] init]];

更新 3 - 示例 2:

我没有展示 MyAsyncBlockOperation 的实现,但您可以使用 这是它基于 Swift 的内容.

I am not showing the implementation of MyAsyncBlockOperation but you can use this as what it's based on in Swift.

// operation.asynchronous is YES, and things are implemented correctly for state changes.
MyAsyncBlockOperation *operation = [MyAsyncBlockOperation new];
__weak MyAsyncBlockOperation *weakOperation = operation;
// executionBlock is simply invoked in main
// - (void)main { self.executionBlock() };
operation.executionBlock = ^{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Vendor"
                                                                             message:@"Should vendor do work?"
                                                                      preferredStyle:UIAlertControllerStyleAlert];

    [alertController addAction:[UIAlertAction actionWithTitle:@"Yes"
                                                        style:UIAlertActionStyleDefault
                                                      handler:^(UIAlertAction *action) {
                                                          [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                                                              NSLog(@"Never called");
                                                              [weakOperation completeWithSuccess];
                                                          }];
                                                      }]];

    [alertController addAction:[UIAlertAction actionWithTitle:@"No"
                                                        style:UIAlertActionStyleDefault
                                                      handler:^(UIAlertAction *action) {
                                                          [weakOperation cancel];
                                                      }]];

    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
}];

operation.completionBlock = ^{
    NSLog(@"If YES, Never called. If NO, called.");
};

[[NSOperationQueue mainQueue] addOperation:operation];

所以我想,为什么不用另一个 NSOperationQueue 呢?其中一个 underlyingQueue 设置为前面提到的间接GCD 队列(仍在遵循文档).所以我们可以有一个并发的 NSOperationQueue,合法地针对串行主 GCD 队列,并最终确保操作在主线程上运行.

So I thought, why not have another NSOperationQueue? One with it's underlyingQueue set to the previously mentioned indirect GCD queue (still following the documentation). So we can have a concurrent NSOperationQueue, legally targeting the serial main GCD queue, and ultimately ensuring the operations run on the main thread.

如果您想澄清,请告诉我,这是完整代码的示例:

Let me know if you want clarification, here is an example of the full code:

NSOperationQueue *asyncSafeMainQueue = [[NSOperationQueue alloc] init];
asyncSafeMainQueue.qualityOfService = NSQualityOfServiceDefault; // not needed, just for clarity
dispatch_queue_t underlyingQueue = dispatch_queue_create("com.mydomain.main-thread", NULL);
dispatch_set_target_queue(underlyingQueue, dispatch_get_main_queue());
asyncSafeMainQueue.underlyingQueue = underlyingQueue;

现在...有一个安全的操作队列用于需要在主线程上运行的异步操作,并且没有任何不必要的上下文切换.

安全吗?

推荐答案

嗯.. 如果使用 setTarget 而不是指定的构造函数,这会在 Swift-4 中严重崩溃..

Hmm.. This crashes very badly in Swift-4 if using setTarget instead of the designated constructor..

如果您使用 Objective-C 桥接,那么您可以:

If you use Objective-C bridging, then you can do:

@interface MakeQueue : NSObject
+ (NSOperationQueue *)makeQueue:(bool)useSerial;
@end

@implementation MakeQueue
+ (NSOperationQueue *)makeQueue:(bool)useSerial {
    dispatch_queue_t serial = dispatch_queue_create("serial", nil);
    dispatch_queue_t concurrent = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);

    dispatch_queue_t queue = useSerial ? serial : concurrent;
    dispatch_set_target_queue(queue, dispatch_get_main_queue());

    NSOperationQueue *opq = [[NSOperationQueue alloc] init];
    opq.underlyingQueue = queue;
    opq.maxConcurrentOperationCount = 8;
    return opq;
}
@end

如果使用 Swift,你有:

and if using Swift, you have:

func makeQueue(_ useSerial: Bool) -> OperationQueue? {

    let testCrash: Bool = false
    var queue: DispatchQueue!

    if testCrash {
        let serial = DispatchQueue(label: "serial")
        let concurrent = DispatchQueue(label: "concurrent", attributes: .concurrent)
        queue = useSerial ? serial : concurrent
        queue.setTarget(queue: DispatchQueue.main)
    }
    else {
        let serial = DispatchQueue(label: "serial", qos: .default, attributes: .init(rawValue: 0), autoreleaseFrequency: .inherit, target: DispatchQueue.main)
        let concurrent = DispatchQueue(label: "concurrent", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: DispatchQueue.main)
        queue = useSerial ? serial : concurrent
    }

    let opq = OperationQueue()
    opq.underlyingQueue = queue
    opq.maxConcurrentOperationCount = 8;
    return opq
}

所以现在我们测试一下:

So now we test it:

class ViewController : UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        //Test Objective-C
        let operationQueue = MakeQueue.makeQueue(false)!
        operationQueue.addOperation {
            self.download(index: 1, time: 3)
        }

        operationQueue.addOperation {
            self.download(index: 2, time: 1)
        }

        operationQueue.addOperation {
            self.download(index: 3, time: 2)
        }


        //Test Swift
        let sOperationQueue = makeQueue(false)!
        sOperationQueue.addOperation {
            self.download(index: 1, time: 3)
        }

        sOperationQueue.addOperation {
            self.download(index: 2, time: 1)
        }

        sOperationQueue.addOperation {
            self.download(index: 3, time: 2)
        }
    }

    func download(index : Int, time: Int){
        sleep(UInt32(time))
        print("Index: \(index)")
    }
}

在任何情况下,maxConcurrentOperations 是什么似乎都无关紧要.如果底层队列是串行的,那么设置这个值似乎什么都不做.但是,如果底层队列是并发的,它限制了一次可以运行的操作数.

In any case, it doesn't seem to matter what the maxConcurrentOperations are.. if the underlying queue is serial, then setting this value seems to do NOTHING.. However, if the underlying queue is concurrent, it places a limit on how many operations can be ran at once.

总而言之,一旦底层队列是 MainQueue 或任何串行队列,所有操作都会(串行)提交给它并阻塞(它等待,因为它是串行队列).

So all in all, once the underlying queue is MainQueue or any serial-queue, all the operations get submitted to it (serially) and they block (it waits because it is serial queue).

如果我们已经在使用指定的队列,我不确定底层队列的意义是什么..但无论如何,将其设置为 main 会导致所有内容都在主队列上连续运行,而不管最大并发计数.

I'm not sure what the point of the underlying queue is if we're already using a designated queue anyway.. but in any case, setting it to main causes everything to run on the main queue and serially regardless of max concurrent count.

这个:https://gist.github.com/jspahrsummers/dbd861d425d783bd2e5a我能找到的唯一用例......并且您可以独立地恢复/暂停自定义队列上的任务,即使其底层队列是主队列或其他一些队列.AND 挂起/恢复所有其他队列所针对的一个队列,将依次挂起/恢复所有其他队列.

This: https://gist.github.com/jspahrsummers/dbd861d425d783bd2e5a is the only use-case that I could find.. AND that you can independently resume/suspend tasks on your custom queue even if its underlying queue is main or some other queue. AND suspending/resuming the one queue that all other queues target, will in turn suspend/resume all other queues.

这篇关于以下是 dispatch_set_target_queue() 的安全使用吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-14 17:47