5

I have a situation where I'm creating many Cocoa objects in a loop using async/await, and the memory spikes because the objects are only released when the loop is over (instead of every iteration).

The solution would be to use an autoreleasepool. However, I can't seem to get autoreleasepool to work with async/await.

Here is an example:

func getImage() async -> NSImage? {
    return NSImage(named: "imagename") // Do some work
}

Task {
    // This leaks
    for _ in 0 ..< 1000000 {
        let image = await getImage()
        print(image!.backgroundColor)
    }
}

The memory spikes all the way up to 220MB, which is a bit too much for me.

Normally, you could wrap the inner loop in a autoreleasepool, and it would fix the problem, but when I try it with an async function, I get this error:

Cannot pass function of type '() async -> ()' to parameter expecting synchronous function type

Is there any way around this? Or is there another method to accomplish the same goal of releasing the Cocoa objects inside of the loop?

George
  • 25,988
  • 10
  • 79
  • 133
recaptcha
  • 253
  • 1
  • 7
  • Nevertheless that is not a "leak". The memory is _used_ during the loop but it is released when it's all over, correct? – matt Aug 28 '21 at 19:23
  • 1
    @matt Yes, you're right. My bad. However the memory spikes a lot inside the loop – recaptcha Aug 28 '21 at 19:24
  • Dispatch queues and NSThreads manage their own autorelease pool. So, _maybe_ **Task** does the same? If it does, just wrap your inner async function into a Task. I didn't check this myself, though. But it would be very interesting to know! ;) – CouchDeveloper Aug 29 '21 at 13:50
  • 1
    You should post about this on the Swift forums. https://forums.swift.org/ I haven't seen any discussion about how auto-release pools and actors interact. It might have been an overlooked detail. Worth bringing it up! – Alexander Aug 30 '21 at 16:17
  • 1
    I'm not able to reproduce the difficulty. `autoreleasepool` compiles and works for me just fine inside an `async` method. – matt Sep 07 '21 at 18:21
  • @matt the issue is trying to call an async method inside the autoreleasepool block – Germán Jan 31 '23 at 22:37

1 Answers1

8

@CouchDeveloper mentioned to wrap getImage in a Task, as it has its own autoreleasepool, and it seems to work!

Just change

func getImage() async -> NSImage? {
    return NSImage(named: "imagename") // Do some work
}

to

func getImage() async -> NSImage? {
    await Task {
        return NSImage(named: "imagename") // Do some work
    }.value
}
matt
  • 515,959
  • 87
  • 875
  • 1,141
recaptcha
  • 253
  • 1
  • 7