Background task: unexpected return of None

What I’m trying to do:
I am trying set up a timer to follow a background task until it is completed to load data into a table.

What I’ve tried and what’s not working:
I believe this could be a potential bug. I start a background task and have a timer to check if the task is complete. I was originally using the is_completed() attribute of the task object. But I noticed, when the timer is checking the task too frequently, it returns true when the task is not complete. I found this out by using the get_termination_status which returns “completed” when the task is completed as well.

I will note all documentation has task interaction through server functions and I am using the task attributes directly on the client side. Not sure if that could be an issue.

Code Sample:
starting the background task:

self.task = anvil.server.call_s('start_order_background_task',start=None,finish=None)    

Timer:

 with anvil.server.no_loading_indicator:
        complete_flag = self.task.is_completed()
        term_status = self.task.get_termination_status()
        app_logger.debug(f"{self.type} : {term_status} and the complete flag is {complete_flag}")
        if complete_flag and term_status == "completed":
            DO STUFF

Here are the logs:

app_log-DEBUG 2022-05-05 09:04:59: Order : None and the complete flag is True

app_log-DEBUG 2022-05-05 09:05:01: Order : completed and the complete flag is False

I can’t submit a clone on this application, but I can try to duplicate it in a simple example if that is necessary!

Adding a bit more detail.

in the phrase DO STUFF

I was attempting to get the return value from the background task like so:

data = self.task.get_return_value()
self.set_data(data)
self.init_load_flag = True

Unfortunately transitioning to get_termination_status() still was yielding a None from ```get_return_value()``

I resolved this by changing the statement a little:

data = self.task.get_return_value()
          if data is not None:
            self.set_data(data)
            self.init_load_flag = True

But I still feel as though the termination status or completed flag should not be changed until the return value is ready to return.

I might be mis-remembering but I think I have gone through all of the same steps you did when creating something using background tasks in the past. I found the same issues with .is_completed() not being very reliable and ended up just using the timer to ask if task_object is not None: over and over just like you settled on.

Or it does both, if is .is_completed() is True, then it checks for None on the task object, I’m not sure.

I’ll do some digging in my old projects…

1 Like

I let the form manage the UI and only call server functions. My forms do not interact with data tables, background tasks or other services (not a black and white rule, there are exceptions, but mostly true).

In this case I would have the form calling two server functions:

    self.task_id = anvil.server.call_s('start_task')

def timer_tick(self, **event_args):
    status, result = anvil.server.call_s('get_task_status', self.task_id)
    if status != 'done':
        self.status_label.text = status
        return
    timer.interval = 0  # stop polling
    # do something with result

And the server module would have three functions:

  • start_task that:
    • generates an unique id
    • adds a row to the task table
    • launches the background task passing the unique id to it
    • returns the unique id
  • background_task that:
    • uses the task id to get the task row from the task table
    • does its long lasting job while updating the row['status'] column
    • finishes by:
      • writing the result to whatever table (either another column of the same row or another table)
      • updating the row['status'] column with 'done'
  • get_task_status that:
    • uses the task id to get the task row from the table
    • If row['status'] == 'done' returns a tuple with 'done' and whatever value is the result of the background task
    • Otherwise, returns (row['status'], None)

The problem with this approach is that you don’t know when the background task crashes. You will just keep getting the same status.

If you can live with this problem, maybe by adding a timeout, then (in my experience) this approach is more reliable than playing with the Anvil task objects.

1 Like

Ok here is what I have, like @stefano.menci I don’t run a background task directly from the client.
I pass it to a sever call that generates the task object and returns it, just to keep before and after cleanup of running a background task on the server module side. It just returns a task object.

  
  def generate_mfg_upc_codes(self, mfg):
    try:
      if self.mfg_codes_task is None:
        self.mfg_codes_task = anvil.server.call('generate_request_mfg_barcodes_task', mfg)
      elif self.mfg_codes_task is not None:
        task_result = self.mfg_codes_task.get_return_value()
        if task_result is not None:
          ... #  blah blah code stuff in the ui

          self.mfg_codes_task = None
    except anvil.server.TimeoutError as err:
      print(str(err))
      self.mfg_codes_task = None
    except Exception as err:
      if 'Connection to server failed' in str(err):

        Notification('Try refreshing or getting a better signal',title=str(err))

        self.mfg_codes_task = None
        pass
      else:
        raise

for clarification, in this scenario mfg gets popped off a stack of mfgs to get information about (that will not clear until it gets the information back) , and self.mfg_codes_task gets set to None in the __init__.

This is the result of trying all of the same things tried in the OP but noticing none of them worked without inconsistencies.