You are right that it is a retain cycle, but it's a temporary and harmless one because it'll disappear as soon as the enqueued task is finished.
You still could make self weak in the closure to allow the view controller to disappear before the async task runs, which in turns would allow the task to abort early when if the view controller disappeared.
As mentioned this cycle is temporary. If you change async to asyncAfter(.now() + 10) the cycle will exist for 10 seconds, then break, and with async it breaks much sooner.