以下是dispatch_set_target_queue()的安全使用吗?

时间:2018-01-03 18:43:46

标签: ios nsoperationqueue foundation libdispatch dispatch-queue

我想要做的是创建间接queue targeting 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 NSOperationQueuebecause Apple's documentation clearly states not to use dispatch_get_main_queue()属性。虽然使用间接队列,但从技术上讲,它遵循文档。

所有这一切的原因是因为NSOperationQueue.mainQueue 对于异步操作不安全,因为它可以全局访问,并且maxConcurrentOperationCount 是设为1 。因此,可以使用此操作队列轻松拍摄自己的脚。

更新1

对于这个问题所假设的基础,似乎存在很多混淆。"异步NSOperation"是。需要说明的是,这是基于WWDC session中的概念。特定的概念是使用"操作就绪"和依赖关系管理来管理应用程序中的任务,这意味着异步NSOperations被添加到NSOperationQueues以利用这一点。如果你把这些概念带到这个问题的精神,希望推理会更有意义,你可以专注于比较和对比解决方案。

更新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的实现,但您可以使用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 queue仍然遵循文档)。所以我们可以有一个并发的NSOperationQueue,合法地定位串行主GCD queue,并最终确保操作在主线程上运行。

如果您需要澄清,请告诉我,以下是完整代码的示例:

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;

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

安全吗?

2 个答案:

答案 0 :(得分:0)

我不明白为什么你认为$value = $xml->Data->Value[0]; $value[0] = 1; 对异步操作不安全。您提供的理由会使同步操作不安全(因为您可能会死锁)。

无论如何,我认为尝试你建议的解决方法是个坏主意。 Apple没有解释(在你链接的页面上)为什么你不应该将mainQueue设置为主队列。我建议你安全地玩,并遵循禁止的精神而不是信件。

更新

现在看一下您的更新问题,使用示例代码,我看不到任何可以阻塞主线程/队列的内容,因此不存在死锁的可能性。 underlyingQueue的{​​{1}}为1并不重要。我的示例中没有任何内容需要创建单独的mainQueue或从中获益。

此外,如果macConcurrentOperationCount是一个串行队列(或者在其目标链中的任何地方都有一个串行队列),那么将NSOperationQueue设置为什么并不重要。这些操作仍将连续运行。亲自尝试一下:

underlyingQueue

答案 1 :(得分:0)

嗯..如果使用setTarget而不是指定的构造函数,这在Swift-4中会非常糟糕。

如果使用Objective-C桥接,则可以执行以下操作:

@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,你有:

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
}

现在我们测试一下:

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是什么似乎并不重要..如果底层队列是串行的,那么设置这个值似乎没有。但是,如果底层队列是并发的,它限制了一次可以运行的操作数量。

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

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

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