Cannot call mutating async function 'wait(with:priority:)' or ' cancel(with:)' on actor-isolated property 'prediction'
ConradoMateu opened this issue · comments
I have a problem using prediction.cancel and prediction.wait in a View that contains EnvironmentObjects:
struct PromptView: View {
@EnvironmentObject private var controller: ImageController
@EnvironmentObject private var generator: ImageProducer
@EnvironmentObject private var focusCon: GenerateImageFocusController
@EnvironmentObject private var store: ProjectStore
@State var prediction: Difussion.Prediction? = nil
let client: Replicate.Client = Replicate.Client(token: "exampleToken")
func generate() async throws {
prediction = try await Difussion.predict(with: client,
input: .init(prompt: "Example Prompt"))
try await prediction?.wait(with: client)
}
func cancel() async throws {
try await prediction?.cancel(with: client)
}
var body: some View {
VStack(alignment: .leading, spacing: 6) {
Text("Example")
}
}
}
Here is the error:
But if I delete the EnvironmentObjects The view seems to work:
Do you know what should I do to fix this issue? Since I need these environment objects in the view.
Thanks in advance.
You can reproduce it with this example easier, with the code that you have in the example repo:
import SwiftUI
import Replicate
// Get your API token at https://replicate.com/account
private let client = Replicate.Client(token: "12345")
// https://replicate.com/stability-ai/stable-diffusion
enum StableDiffusion: Predictable {
static var modelID = "stability-ai/stable-diffusion"
static let versionID = "db21e45d3f7023abc2a46ee38a23973f6dce16bb082a930b0c49861f96d1e5bf"
struct Input: Codable {
let prompt: String
}
typealias Output = [URL]
}
class Controller: ObservableObject {
}
struct ContentView: View {
@EnvironmentObject private var controller: Controller
@State private var prompt = ""
@State private var prediction: StableDiffusion.Prediction? = nil
var body: some View {
Form {
Section {
TextField(text: $prompt,
prompt: Text("Enter a prompt to display an image"),
axis: .vertical,
label: {})
.disabled(prediction?.status.terminated == false)
.submitLabel(.go)
.onSubmit(of: .text) {
Task {
try! await generate()
}
}
}
if let prediction {
ZStack {
Color.clear
.aspectRatio(1.0, contentMode: .fit)
switch prediction.status {
case .starting, .processing:
VStack{
ProgressView("Generating...")
.padding(32)
Button("Cancel") {
Task { try await cancel() }
}
}
case .failed:
Text(prediction.error?.localizedDescription ?? "Unknown error")
.foregroundColor(.red)
case .succeeded:
if let url = prediction.output?.first {
VStack {
AsyncImage(url: url, scale: 2.0, content: { phase in
phase.image?
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(32)
})
ShareLink("Export", item: url)
.padding(32)
}
}
case .canceled:
Text("The prediction was canceled")
.foregroundColor(.secondary)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.padding()
.listRowBackground(Color.clear)
.listRowInsets(.init())
}
}
Spacer()
}
func generate() async throws {
prediction = try await StableDiffusion.predict(with: client,
input: .init(prompt: prompt))
try await prediction?.wait(with: client)
}
func cancel() async throws {
try await prediction?.cancel(with: client)
}
}
Hi @ConradoMateu. This is more of a question about SwiftUI than anything specific to Replicate's Swift client. The issue has to do with calling the mutating method prediction.wait(with:)
.
I just opened #38 to make Prediction.wait(for:with:priority:retrier:)
publicly available as a convenience. My understanding is that re-assigning the prediction
property should work as expected.
func generate() async throws {
prediction = try await StableDiffusion.predict(with: client,
input: .init(prompt: prompt))
var retrier = client.retryPolicy.makeIterator()
prediction = try await Prediction.wait(for: prediction, with: client, priority: nil, retrier: &retrier)
}
Thanks for creating #38 🙏
Also, I have read your posts on NSHipster many times, they were really helpful 🚀