-1

我想要做的是创建一个针对主队列的间接队列。

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

我的最终目标是使用队列作为NSOperationQueue的基础队列属性,因为 Apple 的文档明确声明不要使用 dispatch_get_main_queue()。尽管使用间接队列,但它在技术上遵循文档。

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

更新 1

关于这个问题假设“异步 NSOperation”是什么的基础,似乎有很多困惑。需要明确的是,这是基于此WWDC 会话中的概念。特定概念是使用“操作就绪”和依赖管理来管理应用程序中的任务,这意味着将异步 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 的实现,但您可以将其用作它在 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 呢?一个将其底层队列设置为前面提到的间接GCD 队列仍然遵循文档)。所以我们可以有一个并发的 NSOperationQueue,合法地针对串行主GCD 队列,并最终确保操作在主线程上运行。

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

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;

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

安全吗?

4

2 回答 2

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是我能找到的唯一用例。并且即使其底层队列是主队列或其他队列,您也可以在自定义队列上独立恢复/暂停任务队列。并且暂停/恢复所有其他队列目标的一个队列,将依次暂停/恢复所有其他队列。

于 2018-01-04T03:52:47.580 回答
0

我不明白为什么您认为mainQueue异步操作不安全。您给出的原因会使同步操作不安全(因为您可能会死锁)。

无论如何,我认为尝试您建议的解决方法是个坏主意。Apple 没有解释(在您链接的页面上)为什么您不应该设置underlyingQueue到主队列。我建议您谨慎行事,遵循禁令的精神而不是信条。

更新

现在看看你更新的问题,使用示例代码,我看不到任何可以阻塞主线程/队列的东西,所以没有死锁的可能性。mainQueue没有1无关紧要macConcurrentOperationCount。我在您的示例中看不到任何需要或受益于创建单独的NSOperationQueue.

此外,如果它underlyingQueue是一个串行队列(或在其目标链中的任何位置都有一个串行队列),那么您设置什么并不重要maxConcurrentOperationCount。这些操作仍将连续运行。自己试试:

@implementation AppDelegate {
    dispatch_queue_t concurrentQueue;
    dispatch_queue_t serialQueue;
    NSOperationQueue *operationQueue;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    concurrentQueue = dispatch_queue_create("q", DISPATCH_QUEUE_CONCURRENT);
    serialQueue = dispatch_queue_create("q2", nil);
    operationQueue = [[NSOperationQueue alloc] init];

    // concurrent queue targeting serial queue
    //dispatch_set_target_queue(concurrentQueue, serialQueue);
    //operationQueue.underlyingQueue = concurrentQueue;

    // serial queue targeting concurrent queue
    dispatch_set_target_queue(serialQueue, concurrentQueue);
    operationQueue.underlyingQueue = serialQueue;

    operationQueue.maxConcurrentOperationCount = 100;

    for (int i = 0; i < 100; ++i) {
        NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"operation %d starting", i);
            sleep(3);
            NSLog(@"operation %d ending", i);
        }];
        [operationQueue addOperation:operation];
    }
}

@end
于 2018-01-03T20:45:09.453 回答