Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import SwiftUI
- import os
- let log = Logger()
- struct ContentView: View {
- @State var taskInProgress: Task<Void, Never>?
- @State var status: Status = .inactive
- enum Status: Equatable {
- case inactive
- case inProgress
- case cancelRequested
- case userCancelled
- case completed(Int)
- }
- var body: some View {
- VStack {
- Text("Status: \(status)")
- if status == .inProgress {
- }
- switch status {
- case .userCancelled, .completed(_):
- Button(action: {
- status = .inactive
- }, label: {
- Text("OK")
- })
- case .inactive:
- Button(action: {
- status = .inProgress
- log.info("Starting Task")
- taskInProgress = Task { await requestSomething() }
- }, label: {
- Text("Launch task")
- })
- case .inProgress:
- Button(action: {
- status = .cancelRequested
- taskInProgress?.cancel()
- log.info("Task cancelled")
- }, label: {
- Text("Cancel")
- })
- default:
- EmptyView()
- }
- }
- .padding()
- }
- actor CancelOrResult<T: Sendable, E: Error> {
- var handled: Bool = false
- let continuation: CheckedContinuation<T, E>
- init(continuation: CheckedContinuation<T, E>) {
- self.continuation = continuation
- }
- func cancel(with error: E) {
- guard !handled else { return }
- handled = true
- continuation.resume(throwing: error)
- }
- func resume(returning value: T) {
- guard !handled else { return }
- handled = true
- continuation.resume(returning: value)
- }
- func resume(throwing error: E) {
- guard !handled else { return }
- handled = true
- continuation.resume(throwing: error)
- }
- }
- @MainActor
- func requestSomething() async {
- do {
- let result = try await doSomething()
- status = .completed(result)
- } catch {
- status = .userCancelled
- }
- }
- class CancelOrResultWrapper {
- var cancelOrResult: CancelOrResult<Int, Error>?
- }
- func doSomething() async throws -> Int { // needs to throw as soon as Task is cancelled
- let cancelOrResultWrapper = CancelOrResultWrapper()
- let result: Int = try await withTaskCancellationHandler {
- return try await withCheckedThrowingContinuation { continuation in
- let thisCancelOrResult = CancelOrResult<Int, Error>(continuation: continuation)
- cancelOrResultWrapper.cancelOrResult = thisCancelOrResult
- legacyFunction { result in
- Task.detached {
- await thisCancelOrResult.resume(returning: result)
- }
- }
- }
- } onCancel: {
- log.info("onCancel...") // executed, but can't throw from here
- Task {
- await cancelOrResultWrapper.cancelOrResult?.cancel(with: CancellationError())
- }
- }
- try Task.checkCancellation() // throws only after continuation :'(
- log.info("log result is: \(result)")
- return result
- }
- func legacyFunction(completion: @escaping (Int) -> Void) {
- DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) {
- completion(Int.random(in: 1...99))
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement