I have a serial queue in which I am adding task synchronously. This is to prevent the same function being called at same time from multiple points.
This is my code:
var isProcessGoingOn = false
let serialQueue = DispatchQueue(label: "co.random.queue")
func funcA() {
serialQueue.sync {
if isProcessGoingOn {
debugPrint("Returned")
return
} else {
isProcessGoingOn = true
debugPrint("Executing Code")
// This is to mock the n/w call behaviour. In actual code, I would have a n/w hit in this place.
serialQueue.asyncAfter(deadline: .now() + .seconds(2), execute: {
debugPrint("Setting isProcessGoingOn false")
isProcessGoingOn = false
funcA()
//There may be some cases, where I would need to call the funcA from here.
})
}
}
}
Now, let's suppose the function is called two times:
funcA()
funcA()
And I am getting the following output:
"Executing Code"
"Returned"
Now I was expecting a third call to the funcA, but I am not getting that.
Could anyone explain what is the problem here?
Thanks in advance.
As per your code your first method call will execute debugPrint("Executing Code") and also set isProcessGoingOn = true, At the same time you again call your method funcA(), So it's not wait for your nested block which are:
serialQueue.asyncAfter(deadline: .now() + .seconds(2), execute: {
debugPrint("Setting isProcessGoingOn false")
isProcessGoingOn = false
funcA()
})
So, above block will never execute due to deadline: .now() + .seconds(2) code. Because it's going to wait for 2 second, and before this code execute, your second method call will execute debugPrint("Returned") and return from method.
Solution:
You need to remove your 2 second delay from your nested block.
Cons:
This will affected to your method call second time. It will show you error of execution interrupted.
I hope this will helpful to you and you will fin your answer.
Related
I have a function as follows which adds an operation to the operation queue, how do I test the block of code that is being added to the operation queue, I tried using an expectation by passing an analytics observer spy and check if the value is set but it does not seem to work, please point me in the right direction
func firePendingEvent() {
for analyticItem in eventQueue {
eventPriorityQueue.addOperationToQueue (operation: BlockOperation {
self.analyticsObserver?.logEvent(event: analyticItem) // write test to check the event inside this function
}, priority: .normal)
if eventQueue.count > 0 {
eventQueue.remove(at: 0)
}
}
}
The basic idea is that your test has to create an expectation:
let e = expectation(description: "testGetUsers")
And then when your asynchronous method is done, fulfill that expectation:
e.fulfill()
And then, of course, your test has to, after initiating the asynchronous tasks, wait for the expectations:
waitForExpectations(timeout: 10)
But, obviously, you need to know when your function is done. I would suggest giving it an optional completion handler:
func firePendingEvent(completion: (() -> Void)? = nil) {
let group = DispatchGroup()
for analyticItem in eventQueue {
group.enter()
eventPriorityQueue.addOperationToQueue(operation: BlockOperation {
self.analyticsObserver?.logEvent(event: analyticItem)
group.leave()
}, priority: .normal)
}
eventQueue.removeAll()
if let completion = completion {
group.notify(queue: .main, execute: completion)
}
}
(Note, I personally refrain from mutating an array as I iterate through it, so I add all the tasks to the queue and defer the removeAll until I am no longer iterating.)
Anyway, you can now write your test:
func testFirePendingEvents() {
let e = expectation(description: "testGetUsers")
foo.firePendingEvent {
XCAssert(…) // do whatever tests are appropriate
e.fulfill()
}
waitForExpectations(timeout: 10)
}
But, because that completion handler is optional, and defaults to nil, you don't have to change the rest of your code that isn't availing itself of the new completion handler.
Personally, I would add a parameter to that completion handler closure to pass back whatever value you want to test, but there wasn’t enough in the question to know what result you are testing.
I have a func that gets a list of Players. When i fetch the players i need only to show those who belongs to the current Team so i am showing only a subset of the original list by filtering them. I don't know in advance, before making the request, how much players belong to the Team selected by the User, so i may need to do additional requests until i can display on the TableView at least 10 rows of Players. The User by pulling up from the bottom of the TableView can request more players to display. To do this i am calling a first async func request which in turn calls, inside a while, another nested async func request. Here a code to give you an idea of what i am trying to do:
let semaphore = DispatchSemaphore(value: 0)
func getTeamPlayersRequest() {
service.getTeamPlayers(...)
{
(result) in
switch result
{
case .success(let playersModel):
if let validCurrentPage = currentPageTmp ,
let validTotalPages = totalPagesTmp ,
let validNextPage = self.getTeamPlayersListNextPage()
{
while self.playersToShowTemp.count < 10 && self.currentPage < validTotalPages
{
self.currentPage = validNextPage //global var
self.fetchMorePlayers()
self.semaphore.wait() //global semaphore
}
}
case .failure(let error):
//some code...
}
})
}
private func fetchMorePlayers(){
// Completion handler of the following function is never called..
service.getTeamPlayers(requestedPage: currentPage, completion: {
(result) in
switch result
{
case .success(let playersModel):
if let validPlayerList = playersList,
let validPlayerListData = validPlayerList.data,
let validTeamModel = self.teamPlayerModel,
let validNextPage = self.getTeamPlayersListNextPage()
{
for player in validPlayerListData
{
if ( validTeamModel.id == player.team?.id)
{
self.playersToShowTemp.append(player)
}
}
}
self.currentPage = validNextPage
self.semaphore.signal() //global semaphore
case .failure(let error):
//some code...
}
}
}
I have tried both with DispatchGroup and Semaphore but i don't get it what i am doing wrong. I debugged the code and saw that the first async call get executed in a different queue (not the main queue) and a different thread. The nested async call getexecuted on a different thread but i don't know if it's the same concurrent queue of the first async call.
The completion handler of thenested call it's never called. Does anyone know why? is the self.semaphore.wait(), even if it get executed after the fetchMorePlayers() return, blocking/preventing the nested async completion handler to be called?
I am noticing through the Debugger that the completion() in the Xcode vars window has the note "swift partial apply forwarder for closure #1"
If we inline the function call in your loop, it looks something like this:
while self.playersToShowTemp.count < 10 && self.currentPage < validTotalPages
{
self.currentPage = validNextPage //global var
nbaService.getTeamPlayers(requestedPage: currentPage, completion: { ... })
self.semaphore.wait() //global semaphore
}
So nbaService.getTeamPlayers schedules a request, probably on the main DispatchQueue and immediately returns. Then you call wait on your semaphore, which blocks, probably before GCD even tries to run the task scheduled by nbaService.getTeamPlayers.
That's a problem on DispatchQueue.main, which is a serial queue. It has to be a serial queue for UI updates to work. What normally happens is on some iteration of the run loop you make a request, and return.. that bubbles back up to the run loop, which checks for more events and queued tasks. In this case, when your completion handler in getTeamPlayersRequest is waiting to be run, the run loop (via GCD) executes it for that iteration. Then you block the main thread, so the run loop can't continue. If you do need to block always do it on a different DispatchQueue, preferably a .concurrent one.
There is sometimes confusion about what .async does. It only means "run this later and right now return control back to the caller". That's all. It does not guarantee that your closure will run concurrently. It merely schedules it to be run later (possibly soon) on whatever DispatchQueue you called it on. If that queue is a serial queue, then it will be queued to run in its turn in that dispatch queue's run loop. If it's a concurrent queue (ie one you specifically set the attributes to include .concurrent). Then it will run, possibly at the same time as other tasks on that same DispatchQueue.
To avoid that instead of using a loop you can use async-chaining.
private func fetchMorePlayers(while condition: #autoclosure #escaping () -> Bool){
guard condition() else { return }
nbaService.getTeamPlayers(requestedPage: currentPage, completion: {
(result) in
switch result
{
case .success(let playersModel):
if let validPlayerList = playersList,
let validPlayerListData = validPlayerList.data,
let validTeamModel = self.teamPlayerModel,
let validNextPage = self.getTeamPlayersListNextPage()
{
for player in validPlayerListData
{
if ( validTeamModel.id == player.team?.id)
{
self.playersToShowTemp.append(player)
}
}
}
self.currentPage = validNextPage
// Chain to next call
self.fetchMorePlayers(while: condition))
case .failure(let error):
//some code...
}
}
}
Then in getTeamPlayersRequest you can do this:
func getTeamPlayersRequest() {
service.getTeamPlayers(...)
{
(result) in
switch result
{
case .success(let playersModel):
if let validCurrentPage = currentPageTmp ,
let validTotalPages = totalPagesTmp ,
let validNextPage = self.getTeamPlayersListNextPage()
{
self.currentPage = validNextPage //global var
self.fetchMorePlayers(while: self.playersToShowTemp.count < 10 && self.currentPage < validTotalPages)
}
case .failure(let error):
//some code...
}
})
}
This avoids the need to block on a semaphore, because each subsequent request happens in the completion handler of the previously completed one. The only issue is if you need for the completion handler in getTeamPlayersRequest to block while the fetchMorePlayers requests are being fetched, because now it won't you can re-introduce the semaphore. In that case the guard statement in fetchMorePlayers becomes:
guard condition() else
{
self.semaphore.signal()
return
}
That way it only signals on the last completion handler in the chain. You may need to block in a different DispatchQueue though. I think if you need to block, you probably have something about your design that needs to be reconsidered.
If you find yourself reaching for semaphores, it is almost always a mistake. Semaphores are inefficient at best, and introduce deadlock risks if misused. Semaphores should generally be avoided. (Don't get me wrong: Semaphores can be useful in some very narrow use cases, but this is not one of them.)
Use asynchronous patterns. One simple approach might be to recursively call the routine, calling the completion handler when done:
func startFetching(#escaping completion: () -> Void) {
fetchPlayers(page: 0, completion: completion)
}
private func fetchPlayers(page: Int, #escaping completion: () -> Void) {
// prepare request
// now perform request
performRequest(...) { ...
if let error = error {
completion()
return
}
...
if doesNeedMorePlayers {
fetchPlayers(page: page + 1, completion: completion)
} else {
completion()
}
}
}
Personally, I might probably add another closure to emit the players retrieved as we go along, e.g. like, if not actually, a Combine Publisher. Or if you want to update the UI all at once at the very end, just pass the players retrieved thus far as additional parameter in this recursive routine and pass the whole array back in the completion handler. But avoid globals or other state properties.
But the broader idea is to scrupulously avoid semaphores and instead embrace asynchronous patterns.
Why is calling start() for BlockOperation with more then 1 block on a main thread not calling its block on the main thread?
My first test is always passed but second not every time - some times blocks executes not on the main thread
func test_callStartOnMainThread_executeOneBlockOnMainThread() {
let blockOper = BlockOperation {
XCTAssertTrue(Thread.isMainThread, "Expect first block was executed on Main Thread")
}
blockOper.start()
}
func test_callStartOnMainThread_executeTwoBlockOnMainThread() {
let blockOper = BlockOperation {
XCTAssertTrue(Thread.isMainThread, "Expect first block was executed on Main Thread")
}
blockOper.addExecutionBlock {
XCTAssertTrue(Thread.isMainThread, "Expect second block was executed on Main Thread")
}
blockOper.start()
}
Even next code is failed
func test_callStartOnMainThread_executeTwoBlockOnMainThread() {
let asyncExpectation = expectation(description: "Async block executed")
asyncExpectation.expectedFulfillmentCount = 2
let blockOper = BlockOperation {
XCTAssertTrue(Thread.isMainThread, "Expect first block was executed on Main Thread")
asyncExpectation.fulfill()
}
blockOper.addExecutionBlock {
XCTAssertTrue(Thread.isMainThread, "Expect second block was executed on Main Thread")
asyncExpectation.fulfill()
}
OperationQueue.main.addOperation(blockOper)
wait(for: [asyncExpectation], timeout: 2.0)
}
As Andreas pointed out, the documentation warns us:
Blocks added to a block operation are dispatched with default priority to an appropriate work queue. The blocks themselves should not make any assumptions about the configuration of their execution environment.
The thread on which we start the operation, as well as the maxConcurrentOperationCount behavior of the queue, is managed at the operation level, not at the individual execution blocks within an operation. Adding a block to an existing operation is not the same as adding a new operation to the queue. The operation queue governs the relationship between operations, not between the blocks within an operation.
The problem can be laid bare by making these blocks do something that takes a little time. Consider a task that waits one second (you would generally never sleep, but we're doing this simply to simulate a slow task and to manifest the behavior in question). I've also added the necessary “points of interest” code so we can watch this in Instruments, which makes it easier to visualize what’s going on:
import os.log
let pointsOfInterest = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: .pointsOfInterest)
func someTask(_ message: String) {
let id = OSSignpostID(log: pointsOfInterest)
os_signpost(.begin, log: pointsOfInterest, name: "Block", signpostID: id, "Starting %{public}#", message)
Thread.sleep(forTimeInterval: 1)
os_signpost(.end, log: pointsOfInterest, name: "Block", signpostID: id, "Finishing %{public}#", message)
}
Then use addExecutionBlock:
let queue = OperationQueue() // you get same behavior if you replace these two lines with `let queue = OperationQueue.main`
queue.maxConcurrentOperationCount = 1
let operation = BlockOperation {
self.someTask("main block")
}
operation.addExecutionBlock {
self.someTask("add block 1")
}
operation.addExecutionBlock {
self.someTask("add block 2")
}
queue.addOperation(operation)
Now, I'm adding this to a serial operation queue (because you’d never add a blocking operation to the main queue ... we need to keep that queue free and responsive), but you see the same behavior if you manually start this on the OperationQueue.main. So, bottom line, while start will run the operation “immediately in the current thread”, any blocks you add with addExecutionBlock will just run, in parallel, on “an appropriate work queue”, not necessary the current thread.
If we watch this in Instruments, we can see that not only does addExecutionBlock not necessarily honor the thread on which the operation was started, but it doesn’t honor the serial nature of the queue, either, with the blocks running in parallel:
Obviously, if you add these blocks as individual operations, then everything is fine:
for i in 1 ... 3 {
let operation = BlockOperation {
self.someTask("main block\(i)")
}
queue.addOperation(operation)
}
Yielding:
In Java, we can do something like this:
synchronized(a) {
while(condition == false) {
a.wait(time);
}
//critical section ...
//do something
}
The above is a conditional synchronized block, that waits for a condition to become successful to execute a critical section.
When a.wait is executed (for say 100 ms), the thread exits critical section for that duration & some other critical section synchronized by object a executes, which makes condition true.
When the condition becomes successful, next time current thread enters the critical section and evaluates condition, loop exits and code executes.
Important points to note:
1. Multiple critical sections synchronized by same object.
2. A thread is not in critical section for only the duration of wait. Once wait comes out, the thread is in critical section again.
Is the below the proper way to do the same in Swift 4 using DispatchSemaphore?
while condition == false {
semaphore1.wait(duration)
}
semaphore1.wait()
//execute critical section
semaphore1.signal()
The condition could get modified by the time we enter critical section.
So, we might have to do something like below to achieve the Java behavior. Is there a simpler way to do this in Swift?
while true {
//lock
if condition == false {
//unlock
//sleep for sometime to prevent frequent polling
continue
} else {
//execute critical section
//...
//unlock
break
}
}
Semaphores
You can solve this problem with a DispatchSemaphore.
Let's look at this code.
Here we have a semaphore, storage property of type String? and a serial queue
let semaphore = DispatchSemaphore(value: 0)
var storage: String? = nil
let serialQueue = DispatchQueue(label: "Serial queue")
Producer
func producer() {
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
storage = "Hello world!"
semaphore.signal()
}
}
Here we have a function that:
Waits for 3 seconds
Writes "Hello world" into storage
Sends a signal through the semaphore
Consumer
func consumer() {
serialQueue.async {
semaphore.wait()
print(storage)
}
}
Here we have a function that
Waits for a signal from the semaphore
Prints the content of storage
Test
Now I'm going to run the consumer BEFORE the producer function
consumer()
producer()
Result
Optional("Hello world!")
How does it work?
func consumer() {
serialQueue.async {
semaphore.wait()
print(storage)
}
}
The body of the consumer() function is executed asynchronously into the serial queue.
serialQueue.async {
...
}
This is the equivalent of your synchronized(a). Infact, by definition, a serial queue will run one closure at the time.
The first line inside the closure is
semaphore.wait()
So the execution of the closure is stopped, waiting for the green light from the semaphore.
This is happening on a different queue (not the main one) so we are not blocking the main thread.
func producer() {
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
storage = "Hello world!"
semaphore.signal()
}
}
Now producer() is executed. It waits for 3 seconds on a queue different from the main one and then populates storageand send a signal via the semaphore.
Finally consumer() receives the signal and can run the last line
print(storage)
Playground
If you want to run this code in Playground remember to
import PlaygroundSupport
and to run this line
PlaygroundPage.current.needsIndefiniteExecution = true
Answering my question.
Used an instance of NSLock to lock and unlock in the below pseudo code.
while true {
//lock
if condition == false {
//unlock
//sleep for sometime to prevent frequent polling
continue
} else {
//execute critical section
//...
//unlock
break
}
}
I have a nested for loop, and am trying to make it so that the outer loop will only continue once the inner loop and its code is completed, and also add a 1 second delay before performing the next loop.
for _ in 0...3 {
for player in 0...15 {
// CODE ADDING MOVEMENTS TO QUEUE
}
updateBoardArray()
printBoard()
// NEED TO WAIT HERE FOR 1 SEC
}
So I wan't the 0...3 For loop to progress only once the inner loop and update and print functions have completed their cycle, and also a 1 second wait time.
At the moment it all happens at once and then just prints all 4 boards instantly, even when I put that 1 second wait in using DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {}.
Have tried other answers to similar questions but can't seem to get it to work.
As I understand, what you tried to do is the following:
for _ in 0...3 {
for player in 0...15 {
// CODE ADDING MOVEMENTS TO QUEUE
}
updateBoardArray()
printBoard()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {}
}
This will NOT work since what you are doing is add a task (that will do nothing) to the main queue that will get trigged after 1 second, and after adding it, the code continues with the for, without waiting.
Solution
What you could do is simply use sleep(1), but bear in mind that this will freeze the main thread (if you are executing this in the main thread).
To avoid freezing the app you could do is:
DispatchQueue.global(qos: .default).async {
for _ in 0...3 {
for player in 0...15 {
// CODE ADDING MOVEMENTS TO QUEUE
}
DispatchQueue.main.async {
updateBoardArray()
printBoard()
}
sleep(1)
}
}
}
Just keep in mind that any UI action you do, must be done in the main thread
asyncAfter() function takes DispatchTime. DispatchTime is in nanosecond. Following is prototype:
func asyncAfter(deadline: DispatchTime, execute: DispatchWorkItem)
Following is extract of docs from Apple Developer webpage:
DispatchTime represents a point in time relative to the default clock with nanosecond precision. On Apple platforms, the default clock is based on the Mach absolute time unit
To solve the problem, use:
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1000) {}
You can use scheduledTimer:
for i in 0..3{
Timer.scheduledTimer(withTimeInterval: 0.2 * Double(i), repeats: false) { (timer) in
//CODE
}
}
More Info about scheduledTimer.