0

I created a function to add my course events to the calendar app using EventKit.

After learning the swift concurrency, I want to update my code to make the progress much faster, namely using the detached task or TaskGroup to add these events.

Synchronize code without detached task or task group:

func export_test() {
    Task.detached {
        for i in 0...15 {
            print("Task \(i): Start")
            let courseEvent = EKEvent(eventStore: eventStore)
            courseEvent.title = "TEST"
            courseEvent.location = "TEST LOC"
            courseEvent.startDate = .now
            courseEvent.endDate = .now.addingTimeInterval(3600)
            courseEvent.calendar = eventStore.defaultCalendarForNewEvents
            courseEvent.addRecurrenceRule(EKRecurrenceRule(recurrenceWith: .daily, interval: 1, end: nil))
            do {
                try eventStore.save(courseEvent, span: .futureEvents)
            } catch { print(error.localizedDescription) }
            
            print("Task \(i): Finished")
        }
    }
}

Doing the same thing using the TaskGroup :

func export_test() {
    Task.detached {
        await withTaskGroup(of: Void.self) { group in
            for i in 0...15 {
                group.addTask {
                    print("Task \(i): Start")
                    let courseEvent = EKEvent(eventStore: eventStore)
                    courseEvent.title = "TEST"
                    courseEvent.location = "TEST LOC"
                    courseEvent.startDate = .now
                    courseEvent.endDate = .now.addingTimeInterval(3600)
                    courseEvent.calendar = eventStore.defaultCalendarForNewEvents
                    courseEvent.addRecurrenceRule(EKRecurrenceRule(recurrenceWith: .daily, interval: 1, end: nil))
                    do {
                        try eventStore.save(courseEvent, span: .futureEvents)
                    } catch { print(error.localizedDescription) }
                    
                    print("Task \(i): Finished")
                }
            }
        }
    }
}

The output of the TaskGroup version:

Task 0: Start
Task 1: Start
Task 2: Start
Task 4: Start
Task 3: Start
Task 5: Start
Task 6: Start
Task 7: Start
Task 0: Finished
Task 8: Start
Task 1: Finished
Task 9: Start

Sometimes, only a few tasks will been done, and others will not, or even never been started (I created 16 tasks but only printed 9 in this example). Sometimes, all of these events can be added.

In my point of view, I have created 16 child tasks in the TaskGroup.

Each child task will add one event to the Calendar. I think in this way, I can take the full advantage of the multi-core performance (maybe it's actually not. )

If I put the for-loop inside the group.addTask closure, it will always have the expected result, but in this way, we only have a single loop so the TaskGroup may no longer needed.

I'm really exhausted.

snapshot: Snapshot

2 Answers2

0

To see all status messages that the tasks have finished you have to await each result and print it there

func export_test() {
    Task.detached {
        await withTaskGroup(of: String.self) { group in
            for i in 0...15 {
                group.addTask {
                    print("Task \(i): Start")
                    let courseEvent = EKEvent(eventStore: eventStore)
                    courseEvent.title = "TEST"
                    courseEvent.location = "TEST LOC"
                    courseEvent.startDate = .now
                    courseEvent.endDate = .now.addingTimeInterval(3600)
                    courseEvent.calendar = eventStore.defaultCalendarForNewEvents
                    courseEvent.addRecurrenceRule(EKRecurrenceRule(recurrenceWith: .daily, interval: 1, end: nil))
                    do {
                        try eventStore.save(courseEvent, span: .futureEvents)
                    } catch { print(error.localizedDescription) }
                    
                    return "Task \(i): Finished"
                }
            }
            for await taskStatus in group {
                print(taskStatus)
            }
        }
    }
}
vadian
  • 274,689
  • 30
  • 353
  • 361
  • Yeah, I have tried this. I wrapped all the code that creates my courseEvent into another function and return the EKEvent. I `await` for the result like you did, but cannot get these values (I have waited for a long time, exactly longer than 1 minute.) – LiYanan2004 Dec 14 '22 at 09:11
  • I just tested the code successfully in a Playground. Do you have permission to access the store? – vadian Dec 14 '22 at 09:12
  • I have that permission. I tested it again. The for-loop will be execute 16 times ideally, which means there are going to be 16 events added to my calendar. But there is only 8. And it never execute to `for await taskStatus in group`, the console stopped at "Task 9: Start" – LiYanan2004 Dec 14 '22 at 09:31
  • I have just added a snapshot, and you can check that out. [Snapshot](https://i.stack.imgur.com/Ci8i8.png) – LiYanan2004 Dec 14 '22 at 09:36
  • I have solved it by setting commit to `false` in the save method and later on calls `eventStore.commit()` method. – LiYanan2004 Dec 14 '22 at 13:04
0

After a long time researching every aspect of the code. I found that the problem is try eventStore.save(courseEvent, span: .futureEvents).

Alternatively, I use try eventStore.save(..., span: ..., commit: false) and eventStore.commit() to solve the problem.

I think this is caused by the data races because I'm using swift concurrency. While one event is saving, another one calls save method to save again, leading to data conflicts (just my guess.)

To solve this, luckily, we can commit a batch of events later using eventStore.commit() to avoid data conflicts. The result is what I expected !!

And after that optimization, the performance of this function is up to 25% faster (exactly 136ms faster). (haha. Perfect.)

Final Code (in Swift 5.7):

func export_test() {
    Task.detached {
        await withTaskGroup(of: Void.self) { group in
            for i in 0...15 {
                group.addTask {
                    print("Task \(i): Start")
                    let courseEvent = EKEvent(eventStore: eventStore)
                    courseEvent.title = "TEST"
                    courseEvent.location = "TEST LOC"
                    courseEvent.startDate = .now
                    courseEvent.endDate = .now.addingTimeInterval(3600)
                    courseEvent.calendar = eventStore.defaultCalendarForNewEvents
                    courseEvent.addRecurrenceRule(EKRecurrenceRule(recurrenceWith: .daily, interval: 1, end: nil))
                    try eventStore.save(courseEvent, span: .futureEvents, commit: false)
                }
            }
        }
        
        eventStore.commit()
    }
}