I have an async function that synchronizes the network and database from the last call, then returns the results. There are several callers from different threads that calls this function.
Instead of executing and serving the request per call, I'd like to queue up the tasks while the async function runs, then flush out the queue so the next set of tasks can be queued up.
Here's what I came up so far:
extension DataWorker {
// Handle simultanuous pull requests in a queue
private static let pullQueue = DispatchQueue(label: "DataWorker.remotePull")
private static var pullTasks = [((SomeType) -> Void)]()
private static var isPulling = false
func remotePull(completion: ((SomeType) -> Void)?) {
DataWorker.pullQueue.async {
if let completion = completion {
DataWorker.pullTasks.append(completion)
}
guard !DataWorker.isPulling else { return }
DataWorker.isPulling = true
self.store.remotePull { result in
print("Remote pull executed")
DataWorker.pullQueue.async {
let tasks = DataWorker.pullTasks
DataWorker.pullTasks.removeAll()
DataWorker.isPulling = false
DispatchQueue.main.async {
tasks.forEach { $0(result) }
}
}
}
}
}
}
Below is how I'm testing it, which I expect exactly 100 iterations but only a couple of remotePull executions:
DispatchQueue.concurrentPerform(iterations: 100) { iteration in
self.dataWorker.remotePull { _ in
print("Iteration: \(iteration)")
}
}
Is this approach even accurate, or a more elegant or efficient way of achieving this shared task approach?
Related
I have an actor that is processing values and is then publishing the values with a Combine Publisher.
I have problems understanding actors, I thought when using actors in an async context, it would automatically be serialised. However, the numbers get processed in different orders and not in the expected order (see class tests for comparison).
I understand that if I would wrap Task around the for loop that then this would be returned serialised, but my understanding is, that I could call a function of an actor and this would then be automatically serialised.
How can I make my actor thread safe so it publishes the values in the expected order even if it is called from a different thread?
import XCTest
import Combine
import CryptoKit
actor AddNumbersActor {
private let _numberPublisher: PassthroughSubject<(Int,String), Never> = .init()
nonisolated lazy var numberPublisher = _numberPublisher.eraseToAnyPublisher()
func process(_ number: Int) {
let string = SHA512.hash(data: Data(String(number).utf8))
.description
_numberPublisher.send((number, string))
}
}
class AddNumbersClass {
private let _numberPublisher: PassthroughSubject<(Int,String), Never> = .init()
lazy var numberPublisher = _numberPublisher.eraseToAnyPublisher()
func process(_ number: Int) {
let string = SHA512.hash(data: Data(String(number).utf8))
.description
_numberPublisher.send((number, string))
}
}
final class TestActorWithPublisher: XCTestCase {
var subscription: AnyCancellable?
override func tearDownWithError() throws {
subscription = nil
}
func testActor() throws {
let addNumbers = AddNumbersActor()
var numbersResults = [(int: Int, string: String)]()
let expectation = expectation(description: "numberOfExpectedResults")
let numberCount = 1000
subscription = addNumbers.numberPublisher
.sink { results in
print(results)
numbersResults.append(results)
if numberCount == numbersResults.count {
expectation.fulfill()
}
}
for number in 1...numberCount {
Task {
await addNumbers.process(number)
}
}
wait(for: [expectation], timeout: 5)
print(numbersResults.count)
XCTAssertEqual(numbersResults[10].0, 11)
XCTAssertEqual(numbersResults[100].0, 101)
XCTAssertEqual(numbersResults[500].0, 501)
}
func testClass() throws {
let addNumbers = AddNumbersClass()
var numbersResults = [(int: Int, string: String)]()
let expectation = expectation(description: "numberOfExpectedResults")
let numberCount = 1000
subscription = addNumbers.numberPublisher
.sink { results in
print(results)
numbersResults.append(results)
if numberCount == numbersResults.count {
expectation.fulfill()
}
}
for number in 1...numberCount {
addNumbers.process(number)
}
wait(for: [expectation], timeout: 5)
print(numbersResults.count)
XCTAssertEqual(numbersResults[10].0, 11)
XCTAssertEqual(numbersResults[100].0, 101)
XCTAssertEqual(numbersResults[500].0, 501)
}
}
``
Using actor does indeed serialize access.
The issue you're running into is that the tests aren't testing whether calls to process() are serialized, they are testing the execution order of the calls. And the execution order of the Task calls is not guaranteed.
Try changing your AddNumbers objects so that instead of the output order reflecting the order in which the calls were made, they will succeed if calls are serialized but will fail if concurrent calls are made. You can do this by keeping a count variable, incrementing it, sleeping a bit, then publishing the count. Concurrent calls will fail, since count will be incremented multiple times before its returned.
If you make that change, the test using an Actor will pass. The test using a class will fail if it calls process() concurrently:
DispatchQueue.global(qos: .default).async {
addNumbers.process()
}
It will also help to understand that Task's scheduling depends on a bunch of stuff. GCD will spin up tons of threads, whereas Swift concurrency will only use 1 worker thread per available core (I think!). So in some execution environments, just wrapping your work in Task { } might be enough to serialize it for you. I've been finding that iOS simulators act as if they have a single core, so task execution ends up being serialized. Also, otherwise unsafe code will work if you ensure the task runs on the main actor, since it guarantees serial execution:
Task { #MainActor in
// ...
}
Here are modified tests showing all this:
class TestActorWithPublisher: XCTestCase {
actor AddNumbersActor {
private let _numberPublisher: PassthroughSubject<Int, Never> = .init()
nonisolated lazy var numberPublisher = _numberPublisher.eraseToAnyPublisher()
var count = 0
func process() {
// Increment the count here
count += 1
// Wait a bit...
Thread.sleep(forTimeInterval: TimeInterval.random(in: 0...0.010))
// Send it back. If other calls to process() were made concurrently, count may have been incremented again before being sent:
_numberPublisher.send(count)
}
}
class AddNumbersClass {
private let _numberPublisher: PassthroughSubject<Int, Never> = .init()
lazy var numberPublisher = _numberPublisher.eraseToAnyPublisher()
var count = 0
func process() {
count += 1
Thread.sleep(forTimeInterval: TimeInterval.random(in: 0...0.010))
_numberPublisher.send(count)
}
}
var subscription: AnyCancellable?
override func tearDownWithError() throws {
subscription = nil
}
func testActor() throws {
let addNumbers = AddNumbersActor()
var numbersResults = [Int]()
let expectation = expectation(description: "numberOfExpectedResults")
let numberCount = 1000
subscription = addNumbers.numberPublisher
.sink { results in
numbersResults.append(results)
if numberCount == numbersResults.count {
expectation.fulfill()
}
}
for _ in 1...numberCount {
Task.detached(priority: .high) {
await addNumbers.process()
}
}
wait(for: [expectation], timeout: 10)
XCTAssertEqual(numbersResults, Array(1...numberCount))
}
func testClass() throws {
let addNumbers = AddNumbersClass()
var numbersResults = [Int]()
let expectation = expectation(description: "numberOfExpectedResults")
let numberCount = 1000
subscription = addNumbers.numberPublisher
.sink { results in
numbersResults.append(results)
if numberCount == numbersResults.count {
expectation.fulfill()
}
}
for _ in 1...numberCount {
DispatchQueue.global(qos: .default).async {
addNumbers.process()
}
}
wait(for: [expectation], timeout: 5)
XCTAssertEqual(numbersResults, Array(1...numberCount))
}
}
I have a view model with a state property enum that has 3 cases.
protocol ServiceType {
func doSomething() async
}
#MainActor
final class ViewModel {
enum State {
case notLoaded
case loading
case loaded
}
private let service: ServiceType
var state: State = .notLoaded
init(service: ServiceType) {
self.service = service
}
func load() async {
state = .loading
await service.doSomething()
state = .loaded
}
}
I want to write a unit test that asserts that after load is called but before the async function returns, state == .loading .
If I was using completion handlers, I could create a spy that implements ServiceType, captures that completion handler but doesn't call it. If I was using combine I could use a schedular to control execution.
Is there an equivalent solution when using Swift's new concurrency model?
As you're injecting the depencency via a protocol, you're in a very good position for providing a Fake for that protocol, a fake which you have full control from the unit tests:
class ServiceFake: ServiceType {
var doSomethingReply: (CheckedContinuation<Void, Error>) -> Void = { _ in }
func doSomething() async {
// this creates a continuation, but does nothing with it
// as it waits for the owners to instruct how to proceed
await withCheckedContinuation { doSomethingReply($0) }
}
}
With the above in place, your unit tests are in full control: they know when/if doSomething was called, and can instruct how the function should respond.
final class ViewModelTests: XCTestCase {
func test_viewModelIsLoadingWhileDoSomethingSuspends() {
let serviceFake = ServiceFake()
let viewModel = ViewModel(service: serviceFake)
XCTAssertEquals(viewModel.state, .notLoaded)
let expectation = XCTestExpectation(description: "doSomething() was called")
// just fulfilling the expectation, because we ignore the continuation
// the execution of `load()` will not pass the `doSomething()` call
serviceFake.doSomethingReply = { _ in
expectation.fulfill()
}
Task {
viewModel.load()
}
wait(for: [expectation], timeout: 0.1)
XCTAssertEqual(viewModel.state, .loading)
}
}
The above test makes sure doSomething() is called, as you likely don't want to validate the view model state until you're sure the execution of load() reached the expected place - afterall, load() is called on a different thread, so we need an expectation to make sure the test properly waits until the thread execution reaches the expected point.
The above technique is very similar to a mock/stub, where the implementation is replaced with a unit-test provided one. You could even go further, and just have an async closure instead of a continuation-based one:
class ServiceFake: ServiceType {
var doSomethingReply: () async -> Void = { }
func doSomething() async {
doSomethingReply()
}
}
, and while this would give even greater control in the unit tests, it also pushes the burden of creating the continuations on those unit tests.
You can handle this the similar way you were handling for completion handler, you have the choice to either delay the completion of doSomething using Task.sleep(nanoseconds:) or you can use continuation to block the execution forever by not resuming it same as you are doing with completion handler.
So your mock ServiceType for the delay test scenario looks like:
struct HangingSevice: ServiceType {
func doSomething() async {
let seconds: UInt64 = 1 // Delay by seconds
try? await Task.sleep(nanoseconds: seconds * 1_000_000_000)
}
}
Or for the forever suspended scenario:
class HangingSevice: ServiceType {
private var continuation: CheckedContinuation<Void, Never>?
deinit {
continuation?.resume()
}
func doSomething() async {
let seconds: UInt64 = 1 // Delay by seconds
await withCheckedContinuation { continuation in
self.continuation?.resume()
self.continuation = continuation
}
}
}
When creating a render pass in MetalKit is it better in terms of performance to wait for completion or to add a completion handler? If I use the completion handler then I'll end up with a lot of nested closures, but I think waitForCompletion might block a thread. If the completion handler is preferred, is there a better way in Swift to do this without having to use so many nested closures?
For example,
buffer.addCompletionHandler { _ in
... next task
buffer2.addCompletionHandler { _ in
... etc etc
}
}
The other people are right in telling you that this is probably not what you want to do, and you should go educate yourself on how others have created render loops in Metal.
That said, if you actually have use cases for non-blocking versions of waitUntilCompleted or waitUntilScheduled, you can create and use your own until Apple gets around to providing the same.
public extension MTLCommandBuffer {
/// Wait until this command buffer is scheduled for execution on the GPU.
var schedulingCompletion: Void {
get async {
await withUnsafeContinuation { continuation in
addScheduledHandler { _ in
continuation.resume()
}
}
}
}
/// Wait until the GPU has finished executing the commands in this buffer.
var completion: Void {
get async {
await withUnsafeContinuation { continuation in
addCompletedHandler { _ in
continuation.resume()
}
}
}
}
}
But I doubt those properties will improve any code, as all of the task ordering code, necessary to ensure that the "handlers" are added before commit is called, is worse than the callbacks.
let string: String = await withTaskGroup(of: String.self) { group in
let buffer = MTLCreateSystemDefaultDevice()!.makeCommandQueue()!.makeCommandBuffer()!
group.addTask {
await buffer.schedulingCompletion
return "2"
}
group.addTask {
await buffer.completion
return "3"
}
group.addTask {
buffer.commit()
return "1"
}
return await .init(group)
}
XCTAssertEqual(string, "123")
public extension String {
init<Strings: AsyncSequence>(_ strings: Strings) async rethrows
where Strings.Element == String {
self = try await strings.reduce(into: .init()) { $0.append($1) }
}
}
However, while I'm unconvinced on addScheduledHandler being improvable, I think pairing addCompletedHandler and commit has more potential.
public extension MTLCommandBuffer {
/// Commit this buffer and wait for the GPU to finish executing its commands.
func complete() async {
await withUnsafeContinuation { continuation in
self.addCompletedHandler { _ in
continuation.resume()
}
commit()
} as Void
}
}
You are supposed to use MTLCommandQueues and MTLEvents for serializing your GPU work, not completion handlers. Completion handlers are meant to be used only in cases where you need CPU-GPU synchronization. e.g. when you need to read back a result of GPU calculation on a CPU, or you need to add a back pressure, like for example when you only want to draw a certain amount of frame concurrently.
With Swift concurrency, is it possible to have something almost like an 'unnamed' async let?
Here is an example. You have the following actor:
actor MyActor {
private var foo: Int = 0
private var bar: Int = 0
func setFoo(to value: Int) async {
foo = value
}
func setBar(to value: Int) async {
bar = value
}
func printResult() {
print("foo =", foo)
print("bar =", bar)
}
}
Now I want to set foo and bar using the given methods. Simple usage would look like the following:
let myActor = MyActor()
await myActor.setFoo(to: 5)
await myActor.setBar(to: 10)
await myActor.printResult()
However this code is sequentially run. For all intents and purposes, assume setFoo(to:) and setBar(to:) may be a long running task. You can also assume the methods are mutually exclusive (don't share variables & won't affect each other).
To make this code current, async let can be used. However, this just starts the tasks until they are awaited later on. In my example you'll notice I don't need the return value from these methods. All I need is that before printResult() is called, the previous tasks have completed.
I could come up with the following:
let myActor = MyActor()
async let tempFoo: Void = myActor.setFoo(to: 5)
async let tempBar: Void = myActor.setBar(to: 10)
let _ = await [tempFoo, tempBar]
await myActor.printResult()
Explicitly creating these tasks and then awaiting an array of them seems incorrect. Is this really the best way?
This can be achieved with a task group using withTaskGroup(of:returning:body:). The method calls are individual tasks, and then we await waitForAll() which continues when all tasks have completed.
Code:
await withTaskGroup(of: Void.self) { group in
let myActor = MyActor()
group.addTask {
await myActor.setFoo(to: 5)
}
group.addTask {
await myActor.setBar(to: 10)
}
await group.waitForAll()
await myActor.printResult()
}
I made your actor a class to allow concurrent execution of the two methods.
import Foundation
final class Jeep {
private var foo: Int = 0
private var bar: Int = 0
func setFoo(to value: Int) {
print("begin foo")
foo = value
sleep(1)
print("end foo \(value)")
}
func setBar(to value: Int) {
print("begin bar")
bar = value
sleep(2)
print("end bar \(bar)")
}
func printResult() {
print("printResult foo:\(foo), bar:\(bar)")
}
}
let jeep = Jeep()
let blocks = [
{ jeep.setFoo(to: 1) },
{ jeep.setBar(to: 2) },
]
// ...WORK
RunLoop.current.run(mode: RunLoop.Mode.default, before: NSDate(timeIntervalSinceNow: 5) as Date)
Replace WORK with one of these:
// no concurrency, ordered execution
for block in blocks {
block()
}
jeep.printResult()
// concurrency, unordered execution, tasks created upfront programmatically
Task {
async let foo: Void = blocks[0]()
async let bar: Void = blocks[1]()
await [foo, bar]
jeep.printResult()
}
// concurrency, unordered execution, tasks created upfront, but started by the system (I think)
Task {
await withTaskGroup(of: Void.self) { group in
for block in blocks {
group.addTask { block() }
}
}
// when the initialization closure exits, all child tasks are awaited implicitly
jeep.printResult()
}
// concurrency, unordered execution, awaited in order
Task {
let tasks = blocks.map { block in
Task { block() }
}
for task in tasks {
await task.value
}
jeep.printResult()
}
// tasks created upfront, all tasks start concurrently, produce result as soon as they finish
let stream = AsyncStream<Void> { continuation in
Task {
let tasks = blocks.map { block in
Task { block() }
}
for task in tasks {
continuation.yield(await task.value)
}
continuation.finish()
}
}
Task {
// now waiting for all values, bad use of a stream, I know
for await value in stream {
print(value as Any)
}
jeep.printResult()
}
I have an app that does some processing given a string, this is done in 2 Tasks. During this time i'm displaying an animation. When these Tasks complete i need to hide the animation. The below code works, but is not very nice to look at. I believe there is a better way to do this?
let firTask = Task {
/* Slow-running code */
}
let airportTask = Task {
/* Even more slow-running code */
}
Task {
_ = await firTask.result
_ = await airportTask.result
self.isVerifyingRoute = false
}
Isn't the real problem that this is a misuse of Task? A Task, as you've discovered, is not really of itself a thing you can await. If the goal is to run slow code in the background, use an actor. Then you can cleanly call an actor method with await.
let myActor = MyActor()
await myActor.doFirStuff()
await myActor.doAirportStuff()
self.isVerifyingRoute = false
However, we also need to make sure we're on the main thread when we talk to self — something that your code omits to do. Here's an example:
actor MyActor {
func doFirStuff() async {
print("starting", #function)
await Task.sleep(2 * 1_000_000_000)
print("finished", #function)
}
func doAirportStuff() async {
print("starting", #function)
await Task.sleep(2 * 1_000_000_000)
print("finished", #function)
}
}
func test() {
let myActor = MyActor()
Task {
await myActor.doFirStuff()
await myActor.doAirportStuff()
Task { #MainActor in
self.isVerifyingRoute = false
}
}
}
Everything happens in the right mode: the time-consuming stuff happens on background threads, and the call to self happens on the main thread. A cleaner-looking way to take care of the main thread call, in my opinion, would be to have a #MainActor method:
func test() {
let myActor = MyActor()
Task {
await myActor.doFirStuff()
await myActor.doAirportStuff()
self.finish()
}
}
#MainActor func finish() {
self.isVerifyingRoute = false
}
I regard that as elegant and clear.
I would make the tasks discardable with an extension. Perhaps something like this:
extension Task {
#discardableResult
func finish() async -> Result<Success, Failure> {
await self.result
}
}
Then you could change your loading task to:
Task {
defer { self.isVerifyingRoute = false }
await firTask.finish()
await airportTask.finish()
}