1

I have troubles with multiple threads. Here is the situation:

I make asynchronous requests to backend and, in some cases, need to cancel these. Canceling the requests happens in a separate thread. All requests to the backend are canceled and, as I exit a screen, multiple class instances are deallocated.

When I have the order request-cancel, everything works fine. However, sometimes the cancel method is invoked when I am already in the middle of the finish method (which needs a little bit time because of decoding and conversion of data). In such cases, the app crashes, a messages to deallocated instance is sent. It is not an exception that can be easily cached, I get the crash even when I check for the existence of the instances a line before. Actually, if I understand it right, the instance of the class where the finish and the cancel method are located, is deallocated. Nevermind, the problem is that the thread is switched in the middle of the finish method and I need to prevent that.

My question is: Is there a way to block switching the thread in the middle of a method? To declare this method as a whole (transaction)? Or is there another standard solution for such a problem?

I read this post but I don't really understand whether it can be used in my case or how to do that.

EDIT: Canceling

for (XXX *request in [XXX sharedQueue].operations)
{
    [request setDelegate:nil];
    [request setDownloadProgressDelegate:nil];
    [request setUploadProgressDelegate:nil];
    [request setQueue:nil];
    [request cancel];
}

XXX is the class used for the requests.

Order of the methods

Here is the order in which the methods are invoked (in the cases of error). Handler is the class for handling the requests.

Handler 1: make request
Handler 1: finish begin
Handler 2: cancel begin
Handler 2: cancel end
Handler 2: dealloc
Handler 1: dealloc
Handler 1: finish middle

Handler 1 and 2 are two instances of the class. The first one is deallocated in the middle of the finish method, so at the end of this I get a crash. Deallocating it is normal because after cancel I go to another view and basically everything gets deallocated.

My ideas for solution are either to somehow prevent going back to the finish method or to execute the entire finish method before switching the thread. Unfortunately, I have no idea how one of these could be implemented.

4

3 回答 3

2

看来cancel类的方法XXX没有正确实现。

假设有一些XXX响应cancel消息的异步操作类型。为了可靠地运行,必须满足以下要求:

  1. cancel消息可以从任何线程的客户端发送。

  2. 消息cancel可以随时多次发送

  3. 当操作收到cancel消息时,它会停止它的异步任务并在下一个“取消点”正确地清理自己。注意:这可能与cancel方法异步发生。实现需要是“线程安全的”!

  4. 接收方应通知委托它已被取消(例如在失败处理程序中)。

  5. 此外,调用者不需要在发送cancel消息之前重置委托或以任何方式准备接收者。

这些要求需要通过 class 的实现来满足XXX

现在,假设您有一个用于该操作的内部finish方法,并假设这是该操作将执行的最后一个方法。当执行该方法时,并且当操作同时cancel接收到消息时,实现必须保证没有任何效果,因为为时已晚:取消操作的最后机会已经过去。有多种方法可以实现这一点。cancel

如果这是真的,那么以下代码应该正确地取消您的操作:

for (XXX *request in [XXX sharedQueue].operations) {
    [request cancel];
}

编辑:

cancela andfinish方法的“NSOperation like”实现示例:

笔记:

访问ivars必须同步!将通过各种内部方法访问 Ivar。所有访问都将通过一个名为“sync_queue”的私有串行调度队列进行序列化。

- (void) cancel {
    dispatch_async(self.sync_queue, ^{
        if (_isCancelled || _isFinished) {
            return;
        }
        [self.task cancel];  // will sets _isCancelled to YES
        [self finish]; // will set _isFinished to YES
    });
}

- (void) finish 
{
    assert(<execution context equals sync_queue>);

    if (_isFinished) 
        return;

    completionHandler_t onCompletion = self.completionHandler;
    if (onCompletion) {
        id result = self.result;
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            onCompletion(result);
        });
    };

    self.completionHandler = nil;
    self.task = nil;
    self.isExecuting = NO;
    self.isFinished = YES;
}
于 2013-10-16T11:13:53.500 回答
2

以下方法可能会对您有所帮助:

  1. 向您的控制器添加一个标志来管理所有请求;

  2. 当输入请求完成块时,请执行以下操作:

    completionBlock:^() {
       @synchronized(self.myFinishFlag) {
           ...
       }
    }
    
  3. 在你的取消方法中这样做:

    -(void)userCancelledRequests:(id)sender {
    
       @synchronized(self.myFinishFlag) {
           ...
       }
    }
    

这将延迟“userCancelledRequests body if a finish block is currently running (i.e., lockingself.myFinishFlag”的执行)。

希望这可以帮助。

于 2013-10-16T10:37:19.093 回答
0

最好的阻塞方法是在一个专用队列上执行所有操作。当你想取消时,这样做:

dispatch_async(_someQueue, ^{
    for (XXX *request in [XXX sharedQueue].operations)
    {
        [request setDelegate:nil];
        [request setDownloadProgressDelegate:nil];
        [request setUploadProgressDelegate:nil];
        [request setQueue:nil];
        [request cancel];
    }
});

然后当完成回调被触发时,你可以这样做:

dispatch_async(_someQueue, ^{
    if ([request isCancelled] == NO)
    {
        // Process request
    }
});

只要 _someQueue 未标记为并发队列,这应该可以解决问题。

于 2013-10-16T10:41:08.393 回答