【问题标题】:Block switching thread in iOSiOS中的块切换线程
【发布时间】:2013-10-16 10:05:24
【问题描述】:

我遇到了多个线程的问题。情况如下:

我向后端发出异步请求,在某些情况下,需要取消这些请求。取消请求发生在一个单独的线程中。对后端的所有请求都被取消,当我退出屏幕时,多个类实例被释放。

当我收到订单请求取消时,一切正常。但是,有时当我已经在完成方法的中间时会调用取消方法(由于数据的解码和转换需要一点时间)。在这种情况下,应用程序崩溃,向已释放实例发送消息。这不是一个可以轻松缓存的例外,即使我在前一行检查实例是否存在,我也会崩溃。实际上,如果我理解正确的话,finish 和 cancel 方法所在的类的实例会被释放。没关系,问题是在finish方法中间切换了线程,我需要防止这种情况发生。

我的问题是:有没有办法阻止在方法中间切换线程?将此方法声明为一个整体(事务)?或者对于此类问题是否有其他标准解决方案?

我阅读了this post,但我真的不明白它是否可以用于我的情况或如何做到这一点。

编辑:取消

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

XXX 是用于请求的类。

方法的顺序

这是调用方法的顺序(在错误的情况下)。 Handler 是处理请求的类。

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

处理程序 1 和 2 是该类的两个实例。第一个在完成方法的中间被释放,所以最后我崩溃了。释放它是正常的,因为取消后我转到另一个视图,基本上所有东西都被释放了。

我对解决方案的想法是要么以某种方式阻止返回到finish方法,要么在切换线程之前执行整个finish方法。不幸的是,我不知道如何实现其中之一。

【问题讨论】:

  • 您能否详细说明取消是如何完成的?
  • 好的,我在问题中添加了取消方法的代码。

标签: ios objective-c multithreading web-services asynchronous


【解决方案1】:

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

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

  2. 当输入请求完成块时,这样做:

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

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

这将延迟 'userCancelledRequestsbody if a finish block is currently running (i.e., lockingself.myFinishFlag` 的执行。

希望这会有所帮助。

【讨论】:

  • 但是当我在cancel方法的时候,请求已经完成了(因为finish方法被调用了),所以这个请求已经不在队列中了。那么我怎样才能检查它的标志呢?问题不在于请求本身,我不需要它的响应数据。问题是某些实例被取消方法/在取消方法之后释放,但我在完成方法结束时需要它。
  • 我明白了,然后查看我编辑的答案,以了解延迟请求取消执行的方法。
  • 非常感谢您的帮助。我没有用 recursiveLock/sync 解决我的问题,但我也不想让它像那样解决。如果有多个请求并且没有取消,这对应用程序性能将非常不利。但我将这个想法用于旗帜。我将extern 变量添加到另一个类,并将其设置为YES/NO,具体取决于我是否执行了取消。在finish 方法中,我在对self 执行操作之前检查变量。不是最好的解决方案(我想),但应用程序现在不会崩溃。 :)
  • 很高兴知道您解决了这个问题。实际上,互斥体(@synchronized)只是一个设置为开/关的标志。所以你正在自己实现这种功能(没有真正的线程安全,但这取决于你根据你的要求进行评估)。不知道您所说的应用程序性能在没有取消时受到阻碍是什么意思,但那是另一回事......
  • 如果您将完成块安排在并发队列上,这是正确的。在这种情况下,您可以使用 NSCondition 而不是 @synchronize 指令来避免完成块等待另一个完成块完成...
【解决方案2】:

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

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

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

  2. cancel 消息可以在任何时间和多次时间发送。

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

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

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

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

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

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

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

编辑:

cancelfinish 方法的“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;
}

【讨论】:

  • 感谢您的帮助!你是对的,使用[request cancel] 就足够了,我不需要将代表设置为nil。在我用于请求的库中,cancel 不会在我已经在 finish 方法中的请求上调用。这正是我的问题,我无法停止请求,因此崩溃了。现在我发现了如何防止崩溃。 :)
  • @user2320456 请注意:这里,finish 是操作的私有方法。这是操作将执行的 last 操作。另请注意,在任何情况下都会调用finish - 即使这是由处理cancel 消息引起的。但是,当finish 消息已“有序”输入且操作产生结果时,将使用该结果触发完成处理程序。在finish 期间收到的同时发生的cancel 对操作没有影响(无论如何它已经完成) - 并且cancel 消息也不会停止完成处理程序。
【解决方案3】:

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

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-26
  • 1970-01-01
  • 1970-01-01
  • 2013-04-14
  • 1970-01-01
  • 2020-07-19
  • 1970-01-01
  • 1970-01-01
  • 2011-06-02
相关资源
最近更新 更多