How DispatchQueue.main.sync works - ios

Why does this code crash the app if done on the main queue?
DispatchQueue.main.sync
{
NSLog("Start again")
}
Whereas, if done on any other queue, it works.
private let internalQueue = DispatchQueue(label: "RealmDBInternalQueue")
internalQueue.async
{
DispatchQueue.main.sync
{
NSLog("Start again")
}
}
I wanted to know how exactly these instructions were passed to Main from the internal queue and how the internal queue gets to know when work is done.

Related

Performing selector on Main thread from the current thread inside performAndWait block of a NSManagedObjectContext

I have an NSManagedObjectContext which is initialised from newBackgroundContext of the persistentContainer as following:
managedContext = coreDataStack.persistentContainer.newBackgroundContext()
This is how persistentContainer looks like:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "myXCDataModelName")
container.loadPersistentStores(completionHandler: { [weak self] (_, error) in
if let self = self,
let error = error as NSError? {
print("error!")
}
})
return container
}()
I'm using newBackgroundContext to make sure any CRUD operation with CoreData can be done safely, regardless what thread attempts to make changes on the managedContext, instead of making sure or forcing each operation is done on main thread.
I have a saveContext method where I try to perform save operation on managedContext inside performAndWait block as following:
managedContext.performAndWait {
do {
guard UIApplication.shared.isProtectedDataAvailable,
managedContext.hasChanges else {
return
}
try managedContext.save()
} catch {
print("error!")
}
}
It looks like performAndWait is most of the time runs on main thread but when it is run on another thread, the thread checker generates a warning for following check UIApplication.shared.isProtectedDataAvailable since it should be done on main thread.
I decided to run a selector on main thread for this check so I declared a isProtectedDataAvailable Bool by defaulting it to false at class level and then update its value when this selector runs.
#objc private func checker() {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
}
Now I refactored the performAndWait block as following to run the check on main thread if it is called from another thread.
managedContext.performAndWait {
do {
if Thread.isMainThread {
checker()
} else {
print("RUNNING ON ANOTHER THREAD")
Thread.current.perform(#selector(checker),
on: Thread.main,
with: nil,
waitUntilDone: true,
modes: nil)
}
guard isProtectedDataAvailable,
managedContext.hasChanges else {
return
}
try managedContext.save()
} catch {
print("error!")
}
}
It seems to be working fine when I run it on simulator or real device, I generate different core data related operations which would trigger saving context both on main and background threads.
But what happens is, if I put some breakpoints inside performAndWait block and stop execution to examine how the code block is working, it sometimes results in application freeze when I continue execution, like a deadlock occurs. I wonder if it is somehow related to stopping execution with breakpoints or something is wrong with my implementation even though it is working fine without breakpoints, no app freeze or anything.
I'm worried because before going with this solution, I tried synchronizing on main thread by the inspiration from this answer to just switch to main thread to make this check, as following (which resulted in app freeze and I assume a deadlock) inside performAndWait block:
var isProtectedDataAvailable = false
if Thread.isMainThread {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
} else {
DispatchQueue.main.sync {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
}
}
I had to use sync instead of async, because I had to retrieve updated value of isProtectedDataAvailable before proceeding execution.
Any ideas on this?
I assume your question is about the right approach so that the code is working fine and it allows to set any breakpoints without running into deadlocks. :)
Why not try it this way (from what you stated i cannot see reasons against it):
First evaluating UIApplication.shared.isProtectedDataAvailable on the main thread. (I guess that there are more complex conditions that must hold not only UIApplication.shared.isProtectedDataAvailable = true, but to keep it simple ...)
If UIApplication.shared.isProtectedDataAvailable (and otherConditions) evaluates as true continue with data processing and saving on background thread. As managedContext was created as a newBackgroundContext it can be used there.
Instructions on main thread
if UIApplication.shared.isProtectedDataAvailable && otherConditions {
DispatchQueue.global(qos: .userInitiated).async {
dataProcessing()
}
}
Instructions on background thread
static func dataProcessing() {
// get the managedContext
let context = AppDelegate.appDelegate.managedContext
context.performAndWait {
if context.hasChanges {
do {
try context.save()
} catch {
print("error!")
}
}
}
}
For some reason, moving the following check and synchronising with main queue outside of the performAndWait solves all the problems.
No deadlock or app freeze occurs either with break points or without, regardless which thread triggers the method which contains performAndWait
So the method body looks like following now:
var isProtectedDataAvailable = false
if Thread.isMainThread {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
} else {
DispatchQueue.main.sync {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
}
}
managedContext.performAndWait {
do {
guard isProtectedDataAvailable,
managedContext.hasChanges else {
return
}
try managedContext.save()
} catch {
print("error")
}
}

How to suspend dispatch queue inside for loop?

I have play and pause button. When I pressed play button, I want to play async talking inside for loop. I used dispatch group for async method's waiting inside for loop. But I cannot achieve pause.
startStopButton.rx.tap.bind {
if self.isPaused {
self.isPaused = false
dispatchGroup.suspend()
dispatchQueue.suspend()
} else {
self.isPaused = true
self.dispatchQueue.async {
for i in 0..<self.textBlocks.count {
self.dispatchGroup.enter()
self.startTalking(string: self.textBlocks[i]) { isFinished in
self.dispatchGroup.leave()
}
self.dispatchGroup.wait()
}
}
}
}.disposed(by: disposeBag)
And i tried to do with operationqueue but still not working. It is still continue talking.
startStopButton.rx.tap.bind {
if self.isPaused {
self.isPaused = false
self.talkingQueue.isSuspended = true
self.talkingQueue.cancelAllOperations()
} else {
self.isPaused = true
self.talkingQueue.addOperation {
for i in 0..<self.textBlocks.count {
self.dispatchGroup.enter()
self.startTalking(string: self.textBlocks[i]) { isFinished in
self.dispatchGroup.leave()
}
self.dispatchGroup.wait()
}
}
}
}.disposed(by: disposeBag)
Is there any advice?
A few observations:
Pausing a group doesn’t do anything. You suspend queues, not groups.
Suspending a queue stops new items from starting on that queue, but it does not suspend anything already running on that queue. So, if you’ve added all the textBlock calls in a single dispatched item of work, then once it’s started, it won’t suspend.
So, rather than dispatching all of these text blocks to the queue as a single task, instead, submit them individually (presuming, of course, that your queue is serial). So, for example, let’s say you had a DispatchQueue:
let queue = DispatchQueue(label: "...")
And then, to queue the tasks, put the async call inside the for loop, so each text block is a separate item in your queue:
for textBlock in textBlocks {
queue.async { [weak self] in
guard let self = self else { return }
let semaphore = DispatchSemaphore(value: 0)
self.startTalking(string: textBlock) {
semaphore.signal()
}
semaphore.wait()
}
}
FYI, while dispatch groups work, a semaphore (great for coordinating a single signal with a wait) might be a more logical choice here, rather than a group (which is intended for coordinating groups of dispatched tasks).
Anyway, when you suspend that queue, the queue will be preventing from starting anything queued (but will finish the current textBlock).
Or you can use an asynchronous Operation, e.g., create your queue:
let queue: OperationQueue = {
let queue = OperationQueue()
queue.name = "..."
queue.maxConcurrentOperationCount = 1
return queue
}()
Then, again, you queue up each spoken word, each respectively a separate operation on that queue:
for textBlock in textBlocks {
queue.addOperation(TalkingOperation(string: textBlock))
}
That of course assumes you encapsulated your talking routine in an operation, e.g.:
class TalkingOperation: AsynchronousOperation {
let string: String
init(string: String) {
self.string = string
}
override func main() {
startTalking(string: string) {
self.finish()
}
}
func startTalking(string: String, completion: #escaping () -> Void) { ... }
}
I prefer this approach because
we’re not blocking any threads;
the logic for talking is nicely encapsulated in that TalkingOperation, in the spirit of the single responsibility principle; and
you can easily suspend the queue or cancel all the operations.
By the way, this is a subclass of an AsynchronousOperation, which abstracts the complexity of asynchronous operation out of the TalkingOperation class. There are many ways to do this, but here’s one random implementation. FWIW, the idea is that you define an AsynchronousOperation subclass that does all the KVO necessary for asynchronous operations outlined in the documentation, and then you can enjoy the benefits of operation queues without making each of your asynchronous operation subclasses too complicated.
For what it’s worth, if you don’t need suspend, but would be happy just canceling, the other approach is to dispatching the whole for loop as a single work item or operation, but check to see if the operation has been canceled inside the for loop:
So, define a few properties:
let queue = DispatchQueue(label: "...")
var item: DispatchWorkItem?
Then you can start the task:
item = DispatchWorkItem { [weak self] in
guard let textBlocks = self?.textBlocks else { return }
for textBlock in textBlocks where self?.item?.isCancelled == false {
let semaphore = DispatchSemaphore(value: 0)
self?.startTalking(string: textBlock) {
semaphore.signal()
}
semaphore.wait()
}
self?.item = nil
}
queue.async(execute: item!)
And then, when you want to stop it, just call item?.cancel(). You can do this same pattern with a non-asynchronous Operation, too.

Asynchronous DispatchQueue & Multi Threading in Swift4

Here I've a query about Difference between Threads
Difference Between DispatchQueue.global().sync , DispatchQueue.global().async, DispatchQueue.main.sync and DispatchQueue.main.sync
Here's some questions where i do R&D.
Difference Between DispatchQueue.sync vs DispatchQueue.async
Is DispatchQueue.global(qos: .userInteractive).async same as DispatchQueue.main.async
What does main.sync in global().async mean?
main.async vs main.sync() vs global().async in Swift3 GCD
Difference between DispatchQueue.main.async and DispatchQueue.main.sync
When I use DispatchQueue.global().sync, DispatchQueue.global().async and DispatchQueue.main.async below Code it works perfectly
func loadimage(_ url: URL)
{
DispatchQueue.global().sync { // Here i used DispatchQueue.main.async , DispatchQueue.global().async and DispatchQueue.main.async
if let data1 = try? Data(contentsOf: url){
if let img = UIImage(data: data1){
DispatchQueue.main.async {
self.imgView.image = img
}
}
}
}
}
But when I use DispatchQueue.main.sync the application crashes.
func loadimage(_ url: URL)
{
DispatchQueue.main.sync {
if let data1 = try? Data(contentsOf: url){
if let img = UIImage(data: data1){
DispatchQueue.main.async {
self.imgView.image = img
}
}
}
}
}
And I get below error on DispatchQueue.main.sync Here
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
If you call print(Thread.isMainThread) you probably will see 1 as output. If so you code was executed in main queue.
DispatchQueue.main.sync function pause current thread until code will be executed. But if you run in main queue, you pause main queue and try to execute task in main queue. But it paused. If you call it from background queue, we will:
pause bg queue
execute task in main queue
continue in bg queue.
This logic works for serial queues like main queue.
If you perform async task, you just put this task in queue without pause. When all other task in queue will completed, your code start to execute.
If you have concurent queue, this code may work fine. Concurrent queue can switch between task when executing. You can read more about it in this topic.

How to ensure to run some code on same background thread?

I am using realm in my iOS Swift project. Search involve complex filters for a big data set. So I am fetching records on background thread.
But realm can be used only from same thread on which Realm was created.
I am saving a reference of results which I got after searching Realm on background thread. This object can only be access from same back thread
How can I ensure to dispatch code at different time to the same thread?
I tried below as suggested to solve the issue, but it didn't worked
let realmQueue = DispatchQueue(label: "realm")
var orginalThread:Thread?
override func viewDidLoad() {
super.viewDidLoad()
realmQueue.async {
self.orginalThread = Thread.current
}
let deadlineTime = DispatchTime.now() + .seconds(2)
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
self.realmQueue.async {
print("realm queue after some time")
if self.orginalThread == Thread.current {
print("same thread")
}else {
print("other thread")
}
}
}
}
Output is
realm queue after some time
other thread
Here's a small worker class that can works in a similar fashion to async dispatching on a serial queue, with the guarantee that the thread stays the same for all work items.
// Performs submitted work items on a dedicated thread
class Worker {
// the worker thread
private var thread: Thread?
// used to put the worker thread in the sleep mode, so in won't consume
// CPU while the queue is empty
private let semaphore = DispatchSemaphore(value: 0)
// using a lock to avoid race conditions if the worker and the enqueuer threads
// try to update the queue at the same time
private let lock = NSRecursiveLock()
// and finally, the glorious queue, where all submitted blocks end up, and from
// where the worker thread consumes them
private var queue = [() -> Void]()
// enqueues the given block; the worker thread will execute it as soon as possible
public func enqueue(_ block: #escaping () -> Void) {
// add the block to the queue, in a thread safe manner
locked { queue.append(block) }
// signal the semaphore, this will wake up the sleeping beauty
semaphore.signal()
// if this is the first time we enqueue a block, detach the thread
// this makes the class lazy - it doesn't dispatch a new thread until the first
// work item arrives
if thread == nil {
thread = Thread(block: work)
thread?.start()
}
}
// the method that gets passed to the thread
private func work() {
// just an infinite sequence of sleeps while the queue is empty
// and block executions if the queue has items
while true {
// let's sleep until we get signalled that items are available
semaphore.wait()
// extract the first block in a thread safe manner, execute it
// if we get here we know for sure that the queue has at least one element
// as the semaphore gets signalled only when an item arrives
let block = locked { queue.removeFirst() }
block()
}
}
// synchronously executes the given block in a thread-safe manner
// returns the same value as the block
private func locked<T>(do block: () -> T) -> T {
lock.lock(); defer { lock.unlock() }
return block()
}
}
Just instantiate it and let it do the job:
let worker = Worker()
worker.enqueue { print("On background thread, yay") }
You have to create your own thread with a run loop for that. Apple gives an example for a custom run loop in Objective C. You may create a thread class in Swift with that like:
class MyThread: Thread {
public var runloop: RunLoop?
public var done = false
override func main() {
runloop = RunLoop.current
done = false
repeat {
let result = CFRunLoopRunInMode(.defaultMode, 10, true)
if result == .stopped {
done = true
}
}
while !done
}
func stop() {
if let rl = runloop?.getCFRunLoop() {
CFRunLoopStop(rl)
runloop = nil
done = true
}
}
}
Now you can use it like this:
let thread = MyThread()
thread.start()
sleep(1)
thread.runloop?.perform {
print("task")
}
thread.runloop?.perform {
print("task 2")
}
thread.runloop?.perform {
print("task 3")
}
Note: The sleep is not very elegant but needed, since the thread needs some time for its startup. It should be better to check if the property runloop is set, and perform the block later if necessary. My code (esp. runloop) is probably not safe for race conditions, and it's only for demonstration. ;-)

Dispatch queue blocks main thread

I have multiple tasks that I want to put in a serial/concurrent queue running in background thread. Each of the tasks will fetch data from api(async) then copyItem(sync, depends on the res of fetch). The code below blocks main thread. However main thread will not be blocked if I only assign copyItem to the queue. Why can't I run the whole block in background thread ?
let serialQueue = DispatchQueue(label: "queue", qos: .background)
tableView.selectedRowIndexes.forEach { row in
serialQueue.async {
InitData.fetch("someUrl") { initData in
let fileManager = FileManager()
do {
try fileManager.copyItem(atPath: "pathA", toPath: "pathB")
} catch let error {
print(error)
}
}
}
}
This doesn't block main thread:
tableView.selectedRowIndexes.forEach { row in
InitData.fetch("someUrl") { initData in
let fileManager = FileManager()
let workItem = DispatchWorkItem {
do {
try fileManager.copyItem(atPath: "pathA", toPath: "pathB")
} catch let error {
print(error)
}
}
DispatchQueue.global(qos: .background).async(execute: workItem)
}
}
We can deduce from your symptoms that InitData.fetch takes two arguments: a string ("someUrl") and a callback, and that it submits the callback to the main queue for execution. It doesn't matter what queue you were on when you called InitData.fetch. What matters is the queue that InitData eventually (asynchronously) uses to schedule execution of the callback. Maybe you can tell it which queue you want it to use, but apparently in the program you've written, it uses the main queue.

Resources