I have a non-isolated property. Inside this I need to check whether it's being called from the main thread. If it is, then I want to immediately call a main actor isolated function. I have tried the following:
var myProperty: Bool {
if Thread.isMainThread {
performMainActorIsolatedFunction()
}
...
return true
}
However, it gives me the error:
Call to main actor-isolated instance method 'performMainActorIsolatedFunction()' in a synchronous nonisolated context.
How can I get this to work without marking myProperty with #MainActor?
Related
I've been using the CloudKitShare sample code found here as a sample to help me write code for my app. I want to use performWriterBlock and performReaderBlockAndWait as found in BaseLocalCache using a completionHandler without violating the purposes of the design of the code, which focuses on being thread-safe. I include code from CloudKitShare below that are pertinent to my question. I include the comments that explain the code. I wrote comments to identify which code is mine.
I would like to be able to use an escaping completionHandler if possible. Does using an escaping completionHandler still comply with principles of thread-safe code, or does it in any way violate the purpose of the design of this sample code to be thread-safe? If I use an escaping completionHandler, I would need to consider when the completionHandler actually runs relative to other code outside of the scope of the actual perform function that uses the BaseLocalCache perform block. I would for one thing need to be aware of what other code runs in my project between the time the method executes and the time operationQueue in BaseLocalCache actually executes the block of code and thus the completionHandler.
class BaseLocalCache {
// A CloudKit task can be a single operation (CKDatabaseOperation)
// or multiple operations that you chain together.
// Provide an operation queue to get more flexibility on CloudKit operation management.
//
lazy var operationQueue: OperationQueue = OperationQueue()
// This sample ...
//
// This sample uses this dispatch queue to implement the following logics:
// - It serializes Writer blocks.
// - The reader block can be concurrent, but it needs to wait for the enqueued writer blocks to complete.
//
// To achieve that, this sample uses the following pattern:
// - Use a concurrent queue, cacheQueue.
// - Use cacheQueue.async(flags: .barrier) {} to execute writer blocks.
// - Use cacheQueue.sync(){} to execute reader blocks. The queue is concurrent,
// so reader blocks can be concurrent, unless any writer blocks are in the way.
// Note that Writer blocks block the reader, so they need to be as small as possible.
//
private lazy var cacheQueue: DispatchQueue = {
return DispatchQueue(label: "LocalCache", attributes: .concurrent)
}()
func performWriterBlock(_ writerBlock: #escaping () -> Void) {
cacheQueue.async(flags: .barrier) {
writerBlock()
}
}
func performReaderBlockAndWait<T>(_ readerBlock: () -> T) -> T {
return cacheQueue.sync {
return readerBlock()
}
}
}
final class TopicLocalCache: BaseLocalCache {
private var serverChangeToken: CKServerChangeToken?
func setServerChangeToken(newToken: CKServerChangeToken?) {
performWriterBlock { self.serverChangeToken = newToken }
}
func getServerChangeToken() -> CKServerChangeToken? {
return performReaderBlockAndWait { return self.serverChangeToken }
}
// Trial: How to use escaping completionHandler? with a performWriterBlock
func setServerChangeToken(newToken: CKServerChangeToken?, completionHandler: #escaping (Result<Void, Error>)->Void) {
performWriterBlock {
self.serverChangeToken = newToken
completionHandler(.success(Void()))
}
}
// Trial: How to use escaping completionHandler? with a performReaderBlockAndWait
func getServerChangeToken(completionHandler: (Result<CKServerChangeToken, Error>)->Void) {
performReaderBlockAndWait {
if let serverChangeToken = self.serverChangeToken {
completionHandler(.success(serverChangeToken))
} else {
completionHandler(.failure(NSError(domain: "nil CKServerChangeToken", code: 0)))
}
}
}
}
You asked:
Does using an escaping completionHandler still comply with principles of thread-safe code, or does it in any way violate the purpose of the design of this sample code to be thread-safe?
An escaping completion handler does not violate thread-safety.
That having been said, it does not ensure thread-safety, either. Thread-safety is solely a question of whether you ever access some shared resource from one thread while mutating it from another.
If I use an escaping completionHandler, I would need to consider when the completionHandler actually runs relative to other code outside of the scope of the actual perform function that uses the BaseLocalCache perform block.
Yes, you need to be aware that the escaping completion handler is called asynchronously (i.e., later). That is less of a thread-safety concern than a general understanding of the application flow. It is only a question of what you might be doing in that closure.
IMHO, the more important observation is that the completion handler is called on the cacheQueue used internally by BaseLocalCache. So, the caller needs to be aware that the closure is not called on the caller’s current queue, but on cacheQueue.
It should be noted that elsewhere in that project, they employ another common pattern, where the completion handler is dispatched back to a particular queue, e.g., the main queue.
Bottom line, thread-safety is not a question of whether a closure is escaping or not, but rather (a) from what thread does the method call the closure; and (b) what the supplied closure actually does:
Do you interact with the UI? Then you will want to ensure that you dispatch that back to the main queue.
Do you interact with your own properties? Then you will want to make sure you synchronize all of your access with them, either with actors, relying on the main queue, use your own serial queues, or a reader-writer pattern like in the example you shared with us.
If you are ever unsure about your code’s thread-safety, you might consider temporarily turning on TSAN as described in Diagnosing Memory, Thread, and Crash Issues Early
I am developing an API with its own delegate. I provide the caller a property to chose their own callback queue for the delegate methods.
The structure of my API class looks like:
class MyAPI {
weak var delegate: APIDelegate!
let delegateDispatchQueue: DispatchQueue
init(delegate: APIDelegate, delegateDispatchQueue: DispatchQueue) {
self.delegate = delegate
self.delegateDispatchQueue = delegateDispatchQueue
}
// public method definitions ...
}
While mostly I can call the delegate methods asynchronously, in some cases I need to call them synchronously. And that's where I seem to run into problems. If the user of my API calls my methods on the main thread, and they give the delegateDispatchQueue as the main queue, I get a crash when I try to call delegate methods synchronously.
Here is the helper class I'm using to dispatch my delegate calls to hopefully add a bit more flesh to this issue:
// Calls SyncServerDelegate methods on the `delegateDispatchQueue` either synchronously or asynchronously.
class Delegator {
private weak var delegate: SyncServerDelegate!
private let delegateDispatchQueue: DispatchQueue
init(delegate: SyncServerDelegate, delegateDispatchQueue: DispatchQueue) {
self.delegate = delegate
self.delegateDispatchQueue = delegateDispatchQueue
}
// All delegate methods must be called using this, to have them called on the client requested DispatchQueue. If sync is true, delegate method is effectively called synchronously on the `delegateDispatchQueue`. If sync is false, delegate method is called asynchronously on the `delegateDispatchQueue`.
func call(sync: Bool = false, callback: #escaping (SyncServerDelegate)->()) {
if sync {
// This is crashing with: Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
// seemingly because I am doing a sync dispatch on the main thread when I'm already on the main thread. The problem is, I can't compare threads/queues. https://stackoverflow.com/questions/17489098
delegateDispatchQueue.sync { [weak self] in
guard let self = self else { return }
callback(self.delegate)
}
}
else {
delegateDispatchQueue.async { [weak self] in
guard let self = self else { return }
callback(self.delegate)
}
}
}
}
My initial thought on a solution was to internally dispatch methods to another queue. Such as:
class MyAPI {
// ...
private let startQueue = DispatchQueue(label: "SyncServer", qos: .background)
public myAPIMethod() throws {
startQueue.async {
try myAPIMethodAux() // syntax error
}
}
}
but this is currently a non-starter because I am doing error handling in much of my code by throwing errors and the above pattern immediately generates a syntax error. I could re-write code without this form of error handling, but that's a big effort I'm not quite ready to take on.
Thoughts?
Update
I've not solved this yet, but am working around it. I've split my delegate methods into two parts. The main group of them I can call back asynchronously on delegateDispatchQueue. The other group, where I need to call them synchronously, I make no promises about what queue I call them on-- and just use the same queue that my API is currently running on.
Let me show a simplified example of the problem I'm struggling with:
class CarService {
func getCars() -> Single<[Car]> {
return Single.create { observer in
// Here we're using a thread that was defined in subscribeOn().
someCallbackToAPI { cars in
// Here we're using main thread, because of the someCallbackToAPI implementation.
observer(.success(cars))
}
}
}
}
class CarRepository {
func syncCars() -> Completable {
return CarService().getCars()
.flatMapCompletable { cars in
// Here we're using main thread, but we want some background thread.
saveCars(cars)
}
}
}
class CarViewController {
func loadCar() {
CarRepository().syncCars()
.subscribeOn(someBackgroundScheduler)
.observeOn(MainThread)
.subscribe()
}
}
From the bottom: CarViewController wants to sync all the cars from some external API. It defines what thread should be used for the sync with subscribeOn - we don't want to block the UI thread. Unfortunately, underneath, the CarService has to use some external library methods (someCallbackToAPI) that always returns the result in a main thread. The problem is that after receiving the result, all methods below like e.g. saveCars are called in the same main thread. saveCars may block the UI thread because it saves data to database. Of course I could add observeOn between threads between CarService().getCars() and flatMapCompletable, but I want the CarRepository to be dump and know nothing about the threads. It is the CarViewController responsibility to define working thread.
So my question is, is it a way I could get the scheduler passed in subscribeOn method and switch back to the scheduler after receiving the result from someCallbackToApi?
The short answer is no.
As you surmise, the problem is that your someCallbackToAPI is routing to the main thread which is not what you wanted and there's nothing you can do about that short of re-writing someCallbackToAPI. If you are using Alamofire or Moya, I think they have alternative methods that won't call the closure on the main thread but I'm not sure. URLSession does not switch to the main thread so one idea would be to use it instead.
If you want the saveCars to happen on a background thread, you will have to use observeOn to push the computation back onto a background thread from main. The only thing subscribeOn will do is call someCallbackToAPI(_:) on a background thread, it cannot dictate what thread the function will call its closure on.
So something like:
func syncCars() -> Completable {
return CarService().getCars()
.observeOn(someBackgroundScheduler)
.flatMapCompletable { cars in
// Now this will be on the background thread.
saveCars(cars)
}
}
As a final note, an empty subscribe is a code smell. Any time you find your-self calling .subscribe() for anything other than testing purposes, you are likely doing something wrong.
I have the following async recursive code:
func syncData() {
dal.getList(...) { [unowned self] list, error in
if let objects = list {
if oneTime {
oneTime = false
syncOtherStuffNow()
}
syncData() // recurse until all data synced
} else if let error = error {... }
func syncOtherStuffNow() { } // with its own recursion
My understanding is that the recursion will build the call stack until all the function calls complete, at which point they will all unwind and free up the heap.
I also want to trigger another function (syncOtherStuffNow) from within the closure. But don't want to bind it to the closure with a strong reference waiting for it's return (even though it's async too).
How can I essentially trigger the syncOtherStuffNow() selector to run, and not affect the current closure with hanging on to its return call?
I thought of using Notifications, but that seems overkill given the two functions are in the same class.
Since dal.getList() takes a callback I guess it is asynchronous and so the the first syncData starts the async call and then returns immediately which lets syncData() return.
If syncOtherStuffNow() is async it will return immediately and so dataSync() will not wait on it finishing its job and so continue with its execution to the end.
You can test whether sth builds a callstack by putting a breakpoint on every recursion and look on the callstack how many calls of the same function are ontop.
What I do is recurse with asyncAfter, which unwinds the call stack.
Question:
Does deleting an NSManagedObject have to be done with in context.perform / context.performAndWait block ?
Or is it safe to delete the object outside the block ?
Code:
func delete(something: NSManagedObject, context: NSManagedObjectContext) {
context.performAndWait { //Is context.perform / context.performAndWait required to delete an object ?
context.delete(something)
}
}
My thoughts:
Since this code was being called from different threads (both background / main ) it was better to use context.perform / context.performAndWait.
The context might have been created with a specific concurrent type (main / private queue).
The context's concurrent type would need to match that of the thread (main / background) in which the code was being executed.
The block would ensure it runs ok even if a thread with a different mismatched thread type is executing it.
As my personal experience, use performAndWait, because it will wait until operation done. Anyway, both method will run on it's own thread.(context's thread).
From Documentation:
perform(:) and performAndWait(:) ensure the block operations are
executed on the queue specified for the context. The perform(:)
method returns immediately and the context executes the block methods
on its own thread. With the performAndWait(:) method, the context
still executes the block methods on its own thread, but the method
doesn’t return until the block is executed.
In apple documentation it is being said about „...andWait” it works assyncronious.
However „...andWait” should be used to catch the errors inside of perform block...
Moc.performBlock{
for jsonObject in jsonArray {
let your = actions
}
do {
try moc.save()
moc.performBlockAndWait {
do { try moc.save() }
catch { fatalError(„Failure to save context: (error)”) }
}
}...
Better to do it inside in case you have different values / unused values. In most cases ARC (memory management) should fix it.
You should also read here:
Core Data background context best practice