多线程延迟类释放

问题描述

下面我定义了一个解决方案,并在后台线程中使用间隔作为计时器,如下所示:

@weakify(self)

 //IMPORTANT:- Throttle is working exactly the same way debounce works in RX SO DO NOT USE IT.
RACScheduler *bacgroundScheduler = [RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground];

RACSignal *sampler = [RACSignal interval:3 onScheduler: bacgroundScheduler];
enter code here
 // updateListenerPositionSubject is a RACReplaySubject.
    RACSignal *fallbackSignal = [[RACSignal
      merge:@[ self.updateListenerPositionSubject,sampler ]]
      takeuntil:[self.updateListenerPositionSubject ignoreValues]];


    @weakify(self);
    [fallbackSignal subscribeNext:^(id _Nullable x) {
      @strongify(self);
      [self solutionFallBack];
    } error:^(NSError *error) {
      NSLog(@"Error: %@",error);
    } completed:^{
// to make sure subscription get completed when updateListenerPositionSubject sends complete.  
      NSLog(@"Completed");
    }];
  }

和solutionFallBack函数定义如下:

-(void) solutionFallback {
  // block the original solution.
  [self.updateListenerPositionSubject sendCompleted];
  // bunch of conditions
  [self performSwitchWith:shape];
}

如果“解决方案回退”条件满足,视图模型将在一段时间(可能是 30 秒或 1 分钟)后被释放,这并不好,特别是我在 dealloc 中进行卸载。

所以我尝试了不同的解决方案以避免在不同的线程中有“sample”和“ipdateListenerPositionSubject”,我尝试订阅样本信号并采取直到回退条件满足如下:

RACScheduler *bacgroundScheduler = [RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground];
RACSignal *rac_viewmodelWillDealloc = [self rac_signalForSelector:@selector(performSwitchWith:)];
RACSignal *sampler = [[RACSignal interval:self.sceneswitchConfiguration.roundDuration onScheduler:bacgroundScheduler] takeuntil: rac_viewmodelWillDealloc];
    @weakify(self);
    [sampler subscribeNext:^(id _Nullable x) {
      @strongify(self);
      self.backgroundThread = [NSThread currentThread];
      [self solutionFallback];
    } error:^(NSError *error) {
      NSLog(@"Error: %@",error);
    } completed:^{
      NSLog(@"Completed");
    }];

当我确保满足调用“performSwitchWith”的解决方案条件时...我将取消当前后台线程并切换到另一个线程,如下所示:

@weakify(self);
    [self.backgroundThread cancel];
    dispatch_after(dispatch_time(disPATCH_TIME_Now,2 * NSEC_PER_SEC),dispatch_get_main_queue(),^{
      @strongify(self);
   // continue init the new vm process here.
    });

因此,当我在主线程中切换要安排的时间间隔时,一切都按预期进行,并且立即解除分配:

RACSignal *sampler = [[RACSignal
                    interval:self.sceneswitchConfiguration.roundDuration onScheduler:[RACScheduler mainThreadScheduler]] takeuntil:rac_viewmodelWillDealloc];

我想将采样器信号保留在后台线程中并立即释放该类。

解决方法

NSThread cancel 方法不执行抢先取消。它所做的只是设置一个布尔值,您可以在其他线程中检查它。作为the docs say

此方法的语义与用于 Operation 的语义相同。此方法在接收器中设置状态信息,然后由 isCancelled 属性反映。支持取消的线程应定期调用 isCancelled 方法以确定线程是否实际上已被取消,如果已取消则退出。

有关取消和操作对象的详细信息,请参阅 Operation

无论是取​​消 NSThreaddispatch work item 还是 operations,您唯一可以取消当前在另一个线程上运行的内容的时间是,该线程上的代码是否明确编写以支持取消(例如,在计算任务中,定期检查 isCancelled 状态)。

简而言之,使用 cancel 方法无法解决您的问题。


如果我理解正确的话,您是说当从后台线程调用时,某些东西在 self 上保持了 30 多秒,但当您从主线程调用它时则不然。这是一个非常不寻常的场景。

根据所提供的内容,很难说是什么导致了这种情况。 (如果您可以准备一个 MCVE 来显示此问题,而无需所有这些外部依赖项,那就太好了。)

无论如何,我建议几点:

  1. solutionFallback 之前、之中和之后添加日志语句。确认此方法是否需要更长的时间。这有助于您缩小延迟的来源。如果这些方法花费的时间更长,那么这显然是问题所在,您可以自行诊断为什么它们花费的时间比您预期的要长。如果他们立即返回,那么我们知道问题出在其他地方。

  2. 如果您还没有打开主线程检查器,我建议您打开。从历史上看,在后台线程上尝试 UI 更新会导致神秘的延迟,而主线程检查器会立即识别这些问题。

    我不认为它会找到任何因果关系,但您也可以暂时打开线程消毒器,并确保您没有在那里看到任何问题。

    主线程检查器和 TSAN 在 Diagnosing Memory,Thread,and Crash Issues Early 中讨论。

  3. 如果您没有看到对象及时解除分配,请在这 30 多秒的延迟期间按下“调试内存图”按钮。 (请参阅 Gathering Information About Memory Use 或 WWDC 视频 Visual Debugging with Xcode。)这将准确地向您展示是什么保持了挥之不去的强参考。如果您打开“Malloc 堆栈”功能(在 How to debug memory leaks when Leaks instrument does not show them? 或该视频中进行了描述),它不仅会显示强引用的内容,还会显示强引用最初建立的位置。它显然无法告诉您为什么或在哪里没有删除强引用,但至少它会提供一个强引用列表,您可以从中开始分析。