I am trying to implement upload mechanism for my application. However, I have a concurrency issue I couldn't resolve. I sent my requests using async/await with following code. In my application UploadService is creating every time an event is fired from some part of my code. As an example I creation of my UploadService
in a for loop. The problem is if I do not use NSLock
backend service is called multiple times (5 in this case because of loop). But if I use NSLock
it never reaches the .success
or .failure
part because of deadlock I think. Could someone help me how to achieve without firing upload service multiple times and reaching success part of my request.
final class UploadService {
/// If I use NSLock in the commented lines it never reaches to switch result so can't do anything in success or error part.
static let locker = NSLock()
init() {
Task {
await uploadData()
}
}
func uploadData() async {
// Self.locker.lock()
let context = PersistentContainer.shared.newBackgroundContext()
// It fetches data from core data to send it in my request
guard let uploadedThing = Upload.coreDataFetch(in: context) else {
return
}
let request = UploadService(configuration: networkConfiguration)
let result = await request.uploadList(uploadedThing)
switch result {
case .success:
print("success")
case .failure(let error as NSError):
print("error happened")
}
// Self.locker.unlock()
}
}
class UploadExtension {
func createUploadService() {
for i in 0...4 {
let uploadService = UploadService()
}
}
}
CodePudding user response:
A couple of observations:
Never use locks (or wait for semaphores or dispatch groups, etc.) to attempt to manage dependencies between Swift concurrency tasks. This is a concurrency system predicated upon the contract that threads can make forward progress. It cannot reason about the concurrency if you block threads with mechanisms outside of its purview.
Usually you would not create a new service for every upload. You would create one and reuse it.
E.g., either:
func createUploadService() async { let uploadService = UploadService() for i in 0...4 { await uploadService.uploadData(…) } }
Or, more likely, if you might use this same
UploadService
later, do not make it a local variable at all. Give it some broader scope.let uploadService = UploadService() func createUploadService() async { for i in 0...4 { await uploadService.uploadData(…) } }
The above only works in simple
for
loop, because we could simplyawait
the result of the prior iteration.But what if you wanted the
UploadService
keep track of the prior upload request and you couldn’t justawait
it like above? You could keep track of theTask
and have each taskawait
the result of the previous one, e.g.,actor UploadService { var task: Task<Void, Never>? // change to `Task<Void, Error>` if you change it to a throwing method func upload() { … task = Task { [previousTask = task] in // capture copy of previous task (if any) _ = await previousTask?.result // wait for it to finish before starting this one await uploadData() } } }
FWIW, I made this service with some internal state an
actor
(to avoid races).
CodePudding user response:
Since creating Task {}
is part of structured concurrency it inherits environment (e.g MainThread) from the scope where it was created,try using unstructured concurrency's Task.detached
to prevent it from runnning on same scope ( maybe it was called on main thread ) - with creating Task following way:
Task.detached(priority: .default) {
await uploadData()
}