最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - Calling async function from nonisolated code - Stack Overflow

programmeradmin0浏览0评论

I have a non-sendable class Foo which has to conform to NSFilePresenter. The NSFilePresenter protocol forces 'nonisolated' @objc functions. I need to call an async load() function and I don't get how to write it in Swift 6.

// @MainActor is not an option here !
class Foo : NSObject {
  private var operationQueue: OperationQueue?
  // some other mutable properties, no way to make Foo conform to Sendable
  
  // this must stay async because I have to wait for a file coordinator, code not shown for brevity

  public func load() async throws -> sending String {
    let loadurl = URL(string: "bla")!

    
    var errorMain: NSError?

    let text = try await withCheckedThrowingContinuation { continuation in
      fileCoordinator.coordinate(readingItemAt: loadurl, options:.withoutChanges, error: &errorMain) { (readurl) in
        do {
          let text = try String(contentsOf: readurl)
          continuation.resume(returning: text)
        } catch {
          continuation.resume(throwing: MyError.internalError("file coordinator read operation failed"))
        }
      }
      
      if errorMain != nil {
        continuation.resume(throwing: MyError.internalError(errorMain!.localizedDescription))
      }
    }
    
    return(text)
  }
}

extension Foo: NSFilePresenter {
  nonisolated var presentedItemURL: URL? {
    return URL(string: "Bla")
  }
  
  nonisolated var presentedItemOperationQueue: OperationQueue {
    if operationQueue == nil {
      operationQueue = OperationQueue()
    }
    return operationQueue! // unsafe, but not the problem here
  }

  nonisolated func presentedItemDidMove(to newURL: URL) {

    // Question: How to load() safely ?
    
    load() // ERROR: 'async' call in a function that does not support concurrency
    
    Task { // ERROR: Passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure
      await load()
    }

    Task { @MainActor in // ERROR: Task or actor isolated value cannot be sent
      await load()
    }

  }
}

Any suggestions ?

I have a non-sendable class Foo which has to conform to NSFilePresenter. The NSFilePresenter protocol forces 'nonisolated' @objc functions. I need to call an async load() function and I don't get how to write it in Swift 6.

// @MainActor is not an option here !
class Foo : NSObject {
  private var operationQueue: OperationQueue?
  // some other mutable properties, no way to make Foo conform to Sendable
  
  // this must stay async because I have to wait for a file coordinator, code not shown for brevity

  public func load() async throws -> sending String {
    let loadurl = URL(string: "bla")!

    
    var errorMain: NSError?

    let text = try await withCheckedThrowingContinuation { continuation in
      fileCoordinator.coordinate(readingItemAt: loadurl, options:.withoutChanges, error: &errorMain) { (readurl) in
        do {
          let text = try String(contentsOf: readurl)
          continuation.resume(returning: text)
        } catch {
          continuation.resume(throwing: MyError.internalError("file coordinator read operation failed"))
        }
      }
      
      if errorMain != nil {
        continuation.resume(throwing: MyError.internalError(errorMain!.localizedDescription))
      }
    }
    
    return(text)
  }
}

extension Foo: NSFilePresenter {
  nonisolated var presentedItemURL: URL? {
    return URL(string: "Bla")
  }
  
  nonisolated var presentedItemOperationQueue: OperationQueue {
    if operationQueue == nil {
      operationQueue = OperationQueue()
    }
    return operationQueue! // unsafe, but not the problem here
  }

  nonisolated func presentedItemDidMove(to newURL: URL) {

    // Question: How to load() safely ?
    
    load() // ERROR: 'async' call in a function that does not support concurrency
    
    Task { // ERROR: Passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure
      await load()
    }

    Task { @MainActor in // ERROR: Task or actor isolated value cannot be sent
      await load()
    }

  }
}

Any suggestions ?

Share Improve this question edited Feb 1 at 7:36 Chris asked Jan 31 at 15:48 ChrisChris 1,4472 gold badges11 silver badges23 bronze badges 4
  • "I have to wait for a file coordinator, code not shown" Please show it; this is the heart of the problem. Otherwise I'm tempted to say, just mark the whole thing as @preconcurrency, accept any warnings, and kick the can down the road (and file a bug report asking for concurrency-safe versions of these types). – matt Commented Jan 31 at 16:21
  • I have incorporated the missing code. I already started with @preconcurrency but then the FilePresenter callback crash because something is executed in a wrong thread and that (internal Swift6) check crashes the app. Not an option here, unfortunately... – Chris Commented Jan 31 at 23:01
  • Sorry, I don't get it. I don't even understand what open refers to (and neither does the compiler, at my end). What happens if you just write this code using completion handlers alone, as if async/await didn't exist? – matt Commented Jan 31 at 23:18
  • The open() function (now removed) does not add anything to the problem. The load() async function is widely used in the rest of my project (mostly async/await code) and cannot be made sync or changed to completion handler without massive refactoring. But it looks promising to duplicate the fileCoordinator.coordinate(..) closure (which is synchronous) to the also synchronous file presenter callback functions. I will see if I can refactor it in that way. – Chris Commented Feb 1 at 7:41
Add a comment  | 

1 Answer 1

Reset to default 2

You state that @MainActor is not an option here, without further explanation. So without understanding the rest of your project, I can suggest that it might be that class Foo should really be actor Foo in a fully compliant Swift Concurrency world.

The overall issue you are having seems to be that you are trying to pass around an instance of Foo on which you wish to call async methods, without supplying any guarantees to the compiler about that class' data race safety.

A distillation of your code that replicates this compiler problem with only the essential elements would be as follows:

class Foo: NSObject {
    public func load() async throws -> sending String {
        "foo"
    }

    nonisolated func presentedItemDidMove(to newURL: URL) {
        Task { // ❌ Passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure
            try await load()
        }
    }
}

If Foo cannot or should not run as @MainActor, the same guarantee can be given either by making it an actor itself or by taking data safety entirely into your own hands and marking it as @unchecked Sendable, which will get the compiler off your back for better and for worse.

Either of these changes will make the code you have given here compile, however it's impossible to comment on the effect of the changes on the rest of your code.

actor Foo: NSObject {
    // maintains compiler-level data race safety enforcement
}

or...

class Foo: NSObject, @unchecked Sendable {
   // promises the compiler data race safety...
   // ...but removes its ability to enforce this
}
发布评论

评论列表(0)

  1. 暂无评论