Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

169
Visualizações
How to prevent actor reentrancy resulting in duplicative requests?

In WWDC 2021 video, Protect mutable state with Swift actors, they provide the following code snippet:

actor ImageDownloader {
    private var cache: [URL: Image] = [:]

    func image(from url: URL) async throws -> Image? {
        if let cached = cache[url] {
            return cached
        }

        let image = try await downloadImage(from: url)

        cache[url] = cache[url, default: image]

        return cache[url]
    }

    func downloadImage(from url: URL) async throws -> Image { ... }
}

The issue is that actors offer reentrancy, so cache[url, default: image] reference effectively ensures that even if you performed a duplicative request because of some race, that you at least check the actor’s cache after the continuation, ensuring that you get the same image for the duplicative request.

And in that video, they say:

A better solution would be to avoid redundant downloads entirely. We’ve put that solution in the code associated with this video.

But there is no code associated with that video on the website. So, what is the better solution?

I understand the benefits of actor reentrancy (as discussed in SE-0306). E.g., if downloading four images, one does not want to prohibit reentrancy, losing concurrency of downloads. We would, effectively, like to wait for the result of a duplicative prior request for a particular image if any, and if not, start a new downloadImage.

over 4 years ago · Santiago Trujillo
3 Respostas
Responde à pergunta

0

The key is to keep a reference to the Task, and if found, await its value.

Perhaps:

actor ImageDownloader {
    private var cache: [URL: Image] = [:]
    private var tasks: [URL: Task<Image, Error>] = [:]

    func image(from url: URL) async throws -> Image {
        if let image = try await tasks[url]?.value {
            print("found request")
            return image
        }

        if let cached = cache[url] {
            print("found cached")
            return cached
        }

        let task = Task {
            try await download(from: url)
        }

        tasks[url] = task
        defer { tasks[url] = nil }

        let image = try await task.value
        cache[url] = image

        return image
    }

    private func download(from url: URL) async throws -> Image {
        let (data, response) = try await URLSession.shared.data(from: url)
        guard
            let response = response as? HTTPURLResponse,
            200 ..< 300 ~= response.statusCode,
            let image = Image(data: data)
        else {
            throw URLError(.badServerResponse)
        }
        return image
    }
}
over 4 years ago · Santiago Trujillo Relatório

0

After I came up with my original answer, I stumbled across Andy Ibanez’s write-up, Understanding Actors in the New Concurrency Model, in which he does not provide Apple’s code, but provides something inspired by it. The idea is very similar, but he uses an enum to keep track of the cached and pending responses:

actor ImageDownloader {
    private enum ImageStatus {
        case downloading(_ task: Task<UIImage, Error>)
        case downloaded(_ image: UIImage)
    }
    
    private var cache: [URL: ImageStatus] = [:]
    
    func image(from url: URL) async throws -> UIImage {
        if let imageStatus = cache[url] {
            switch imageStatus {
            case .downloading(let task):
                return try await task.value
            case .downloaded(let image):
                return image
            }
        }
        
        let task = Task {
            try await downloadImage(url: url)
        }
        
        cache[url] = .downloading(task)
        
        do {
            let image = try await task.value
            cache[url] = .downloaded(image)
            return image
        } catch {
            // If an error occurs, we will evict the URL from the cache
            // and rethrow the original error.
            cache.removeValue(forKey: url)
            throw error
        }
    }
    
    private func downloadImage(url: URL) async throws -> UIImage {
        let imageRequest = URLRequest(url: url)
        let (data, imageResponse) = try await URLSession.shared.data(for: imageRequest)
        guard let image = UIImage(data: data), (imageResponse as? HTTPURLResponse)?.statusCode == 200 else {
            throw ImageDownloadError.badImage
        }
        return image
    }
}
over 4 years ago · Santiago Trujillo Relatório

0

You can find the “better solution” code in the Developer app. Open the session in the Developer app, select the Code tab, and scroll to “11:59 - Check your assumptions after an await: A better solution”.

screen shot of Developer app

The screen shot is from my iPad, but the Developer app is also available on iPhone, Mac, and Apple TV. (I don't know if the Apple TV version gives you a way to view and copy the code, though…)

As far as I can tell, the code is not available on the developer.apple.com web site, either on the WWDC session's page or as part of a sample project.

For posterity, here is Apple's code. It is extremely similar to that of Andy Ibanez:

actor ImageDownloader {

    private enum CacheEntry {
        case inProgress(Task<Image, Error>)
        case ready(Image)
    }

    private var cache: [URL: CacheEntry] = [:]

    func image(from url: URL) async throws -> Image? {
        if let cached = cache[url] {
            switch cached {
            case .ready(let image):
                return image
            case .inProgress(let task):
                return try await task.value
            }
        }

        let task = Task {
            try await downloadImage(from: url)
        }

        cache[url] = .inProgress(task)

        do {
            let image = try await task.value
            cache[url] = .ready(image)
            return image
        } catch {
            cache[url] = nil
            throw error
        }
    }
}
over 4 years ago · Santiago Trujillo Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda