def METHOD_NAME(self, interval_in_seconds, func, immediately=True):
    """
    @param immediately: whether to run the func immediately when registered
    or wait until `interval_in_seconds` for the first run
    """
    when = time.time() + (0 if immediately else interval_in_seconds)
    with self.heap_lock:
        is_earliest = self._schedule_gathering(when, func, interval_in_seconds)
        # This tests for an edge case where a new interval is supposed to
        # begin before any scheduled gatherings.  We have to awaken at
        # least one gather thread and have it reset to wait for the earlier
        # one.
        if is_earliest:
            self.new_earlier_event.set()
        if len(self.heap) < self.max_thread_count:
            self._add_thread()
    def cancel():
        if cancel.was_called:
            return
        with self.heap_lock:
            # The func should only be in either the heap once, or in a
            # single gather thread awaiting execution.
            for i, (_, heap_func, _) in enumerate(self.heap):
                if heap_func == func:
                    del self.heap[i]
                    heapq.heapify(self.heap)
                    cancel.was_called = True
                    return
            # If the func wasn't in the heap, then it must be currently
            # scheduled gather thread awaiting execution.  This will
            # tell the gather thread to not run it again, nor
            # reschedule it, which will effectively stop the gathering.
            self.func_blacklist.append(func)
        logging.error("Could not find gather event on heap to cancel!")
    cancel.was_called = False
    return cancel