非唯一 NSURLSessionDataTask taskIdentifiers

Posted

技术标签:

【中文标题】非唯一 NSURLSessionDataTask taskIdentifiers【英文标题】:Non-unique NSURLSessionDataTask taskIdentifiers 【发布时间】:2014-02-26 22:27:19 【问题描述】:

我有一个 ios 应用程序,它使用 NSOperationQueue、NSOperations 和 AFNetworking 2.1.0 来触发对服务器的请求。 -[NSOperation main] 方法类似于:

- (void)main 
  AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager sharedSessionManager];
  [sessionManager GET:@"url"
           parameters:nil
              success:^(NSURLSessionDataTask *task, id responseObject) 
                NSLog(@"Success");
              
              failure:^(NSURLSessionDataTask *task, NSError *error) 
                NSLog(@"Failure");
              
  ];

我注意到,当多个操作被创建并快速连续添加到 NSOperationQueue 时,特定操作的回调永远不会被执行。我潜入 AFNetworking 试图找出原因。我最终进入了-[AFURLSessionManager dataTaskWithRequest:completionHandler],看起来像:

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                            completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler

    NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];

    AFURLSessionManagerTaskDelegate *delegate = [AFURLSessionManagerTaskDelegate delegateForManager:self completionHandler:completionHandler];
    [self setDelegate:delegate forTask:dataTask];

    return dataTask;

我在创建 dataTask 后立即添加了一条日志记录语句:

NSLog(@"Task with id %@ created for %@ on queue %@", @(dataTask.taskIdentifier), request.URL.path, dispatch_get_current_queue());

日志揭示了问题:

2014-02-26 14:11:25.071 App[50094:6a2f] Task with id 15 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:25.071 App[50094:460f] Task with id 16 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>

2014-02-26 14:11:26.274 App[50094:6a2f] Task with id 18 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:26.274 App[50094:6c17] Task with id 17 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>

2014-02-26 14:11:27.546 App[50094:6307] Task with id 20 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:27.546 App[50094:6b17] Task with id 19 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>

2014-02-26 14:11:28.705 App[50094:6b17] Task with id 21 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:28.705 App[50094:6307] Task with id 21 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>

2014-02-26 14:11:32.091 App[50094:6307] Task with id 22 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:32.091 App[50094:6b17] Task with id 23 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>

请注意,日志中的第四组具有相同的 taskIdentifier,这是 AFNetworking 通过委托将任务与其回调关联起来的。

如果我强制 NSOperations 在主队列上运行,那么我将无法重新创建问题 - taskIdentifier 始终是唯一的。

有没有人见过这样的事情?我是否需要确保 -[NSURLSession dataTaskWithRequest:] 仅在主线程上运行才能避免出现 taskIdentifier 冲突?

【问题讨论】:

【参考方案1】:

不知道这是否仍然与您有关,但这确切的事情让我整晚都在头疼。事实证明,您正在部分回答问题中的问题。

事实证明,如果

NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];

异步运行,那么NSURLSession 的实例将相同的taskIdentifier 分配给不同任务的可能性很小。

如果不分叉 AFNetworking 并在所有 dataTaskWithRequest: 方法上同步,那么我可以通过两种方法来解决这个问题。

如果您不需要从此方法返回的NSURLSessionTask,那么最好的方法是创建自己的调度队列以及您想使用会话管理器发出的任何请求,您只需像这样将其异步发送到该队列:

static dispatch_queue_t my_queue;
my_queue = dispatch_queue_create("MyQueueName", DISPATCH_QUEUE_CONCURRENT);

// ... Later, that very same day

dispatch_async(my_queue, ^
    // [sessionManager GET: ...
);

但是,这种方法对您的问题的唯一问题是它似乎都是在同一个操作队列上执行的,所以这种方法可能不适用于您的情况。另一种方式(我就是这样做的)实际上更简单。只需在AFURLSessionManager 上同步,这样dataTaskWithRequest: 的调用就只能像这样同步发生:

@synchronized(sessionManager) 
    // [sessionManager GET: ... 

或者,是的,您可以只在主线程上创建任务。但对于某些项目,它并不总是那么简单。

【讨论】:

【参考方案2】:

我会将NSURLSessionDataTask 排入您的AFURLSessionManager 队列,而不是尝试直接发出GET 请求。会话管理器旨在抽象NSURLSession 的功能。

例子:

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    NSURL *URL = [NSURL URLWithString:@"http://example.com/testapi.php"];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) 
        if (error) 
            NSLog(@"error!");
         else 
            NSLog(@"task successful!");
        
    ];
    [dataTask resume];

【讨论】:

这几乎正是 -[AFHTTPSessionManager GET:parameters:success:failure:] 为我所做的。见here

以上是关于非唯一 NSURLSessionDataTask taskIdentifiers的主要内容,如果未能解决你的问题,请参考以下文章

非唯一主索引的使用

唯一与非唯一索引

MySQL 5.0 索引 - 唯一与非唯一

合并具有非唯一索引的多个数据帧

Django:创建索引:非唯一,多列

休眠非唯一对象异常