Assign task to member

So I have a weird problem and I can think of a few workarounds, but they're mostly ugly and I was wondering whether there's a better way of doing this...

Basically I want to have an actor that runs a background task and I want this actor to own the background task. So I'd like to write something like this:

actor Foo {
  let backgroundTask: Task<Never, Error>

  init() {
    backgroundTask = Task {
      await background() // <- compilation error
    }
  }

  deinit() {
    backgroundTask.cancel()
  }
  
  private func backgound() async {
    while (true) {
      // do stuff
    }
  }
}

Now I understand why the above isn't working. The easiest solution seems to be to change the type of the task to an optional, but that seems a bit ugly.

Is there a better way to implement this pattern?

There are a few options. The first is to ask why you want the actor to hide the background task at all: should the actor instead have a func run that the user can call to delegate a Task to the actor? The advantage of that is that it allows the actor's background task to be structured, which this does not.

If for some reason you do really have to have an unstructured Task here, then using an optional is the correct spelling. But I should note that this probably doesn't do what you want: func background keeps self alive (it holds a strong reference), so deinit will never fire unless the background task has already terminated. This is why I ask whether you can have a func run. Right now, the actor owns the Task and the Task owns the actor: if you have func run then this ownership cycle is broken by having the actor not own the Task.

3 Likes

I don't want to make the background task part of the interface. Functionally this actor is a watch for a range of values in a database. For now I am polling for changes, but a future version might use a stream or something like listen/notify.

Oh you're right! Coming from C++ I am still not used to ARC, so when I don't see a smart pointer object I often forget about it :frowning: I guess I could use a weak self in the task to work around this. So in that case in my example above I would create a method private func pollOnce() async and then the task would loop and call this if self can be de-referenced.