Advertisement
Guest User

Cancelable Problem Dave Porier LinkedIn

a guest
Apr 27th, 2024
104
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Swift 3.82 KB | None | 0 0
  1. import SwiftUI
  2. import os
  3.  
  4. let log = Logger()
  5.  
  6. struct ContentView: View {
  7.     @State var taskInProgress: Task<Void, Never>?
  8.     @State var status: Status = .inactive
  9.    
  10.     enum Status: Equatable {
  11.         case inactive
  12.         case inProgress
  13.         case cancelRequested
  14.         case userCancelled
  15.         case completed(Int)
  16.     }
  17.    
  18.     var body: some View {
  19.         VStack {
  20.             Text("Status: \(status)")
  21.            
  22.             if status == .inProgress {
  23.             }
  24.  
  25.             switch status {
  26.             case .userCancelled, .completed(_):
  27.                 Button(action: {
  28.                     status = .inactive
  29.                 }, label: {
  30.                     Text("OK")
  31.                 })
  32.             case .inactive:
  33.                 Button(action: {
  34.                     status = .inProgress
  35.                     log.info("Starting Task")
  36.                     taskInProgress = Task { await requestSomething() }
  37.                 }, label: {
  38.                     Text("Launch task")
  39.                 })
  40.             case .inProgress:
  41.                 Button(action: {
  42.                     status = .cancelRequested
  43.                     taskInProgress?.cancel()
  44.                     log.info("Task cancelled")
  45.                 }, label: {
  46.                     Text("Cancel")
  47.                 })
  48.             default:
  49.                 EmptyView()
  50.             }
  51.         }
  52.         .padding()
  53.     }
  54.    
  55.     actor CancelOrResult<T: Sendable, E: Error> {
  56.         var handled: Bool = false
  57.         let continuation: CheckedContinuation<T, E>
  58.        
  59.         init(continuation: CheckedContinuation<T, E>) {
  60.             self.continuation = continuation
  61.         }
  62.        
  63.         func cancel(with error: E) {
  64.             guard !handled else { return }
  65.             handled = true
  66.             continuation.resume(throwing: error)
  67.         }
  68.        
  69.         func resume(returning value: T) {
  70.             guard !handled else { return }
  71.             handled = true
  72.             continuation.resume(returning: value)
  73.         }
  74.        
  75.         func resume(throwing error: E) {
  76.             guard !handled else { return }
  77.             handled = true
  78.             continuation.resume(throwing: error)
  79.         }
  80.     }
  81.    
  82.     @MainActor
  83.     func requestSomething() async {
  84.         do {
  85.             let result = try await doSomething()
  86.             status = .completed(result)
  87.         } catch {
  88.             status = .userCancelled
  89.         }
  90.     }
  91.    
  92.     class CancelOrResultWrapper {
  93.         var cancelOrResult: CancelOrResult<Int, Error>?
  94.     }
  95.  
  96.     func doSomething() async throws  -> Int { // needs to throw as soon as Task is cancelled
  97.         let cancelOrResultWrapper = CancelOrResultWrapper()
  98.         let result: Int = try await withTaskCancellationHandler {
  99.             return try await withCheckedThrowingContinuation { continuation in
  100.                 let thisCancelOrResult = CancelOrResult<Int, Error>(continuation: continuation)
  101.                 cancelOrResultWrapper.cancelOrResult = thisCancelOrResult
  102.                 legacyFunction { result in
  103.                     Task.detached {
  104.                         await thisCancelOrResult.resume(returning: result)
  105.                     }
  106.                 }
  107.             }
  108.         } onCancel: {
  109.             log.info("onCancel...") // executed, but can't throw from here
  110.             Task {
  111.                 await cancelOrResultWrapper.cancelOrResult?.cancel(with: CancellationError())
  112.             }
  113.         }
  114.         try Task.checkCancellation() // throws only after continuation :'(
  115.         log.info("log result is: \(result)")
  116.         return result
  117.     }
  118.  
  119.     func legacyFunction(completion: @escaping (Int) -> Void) {
  120.         DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) {
  121.             completion(Int.random(in: 1...99))
  122.         }
  123.     }
  124. }
  125.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement