Swift concurrency
Categories:
First, I must acknowledge that my understanding of Swift concurrency is limited. While I have a basic grasp of the subject, if you are reading this and seeking more detailed information, I strongly recommend searching for and reading articles from other sources that provide a more comprehensive explanation of Swift concurrency.
RsyncUI is a graphical user interface (GUI) application; the majority of its operations are executed on the main thread. However, some resource-intensive tasks are performed on other threads managed by the cooperative thread pool (CTP), excluding and not blocking the main thread. How the executors and CTP work and interact are details I don’t know about—they are managed by the Swift runtime. There are three kinds of executors:
- the main executor manages jobs on the main thread
- the global concurrent executor and the serial executor, both execute jobs on threads from the CTP
Most work in RsyncUI is executed on the main thread. By default, SwiftUI ensures all UI updates are performed on the main thread. Below are some tasks within RsyncUI that are executed on the main thread:
- preparing and executing
rsyncsynchronization tasks (preparing involves computing the correct arguments for rsync) - monitoring progress and termination of the actual
rsynctasks- monitoring progress is an asynchronous sequence running on the main thread, created by an asynchronous sequence of two specific notifications generated by the notification center
- some write and read operations
Swift Version 6 and the New Concurrency Model
Swift version 6 introduced strict concurrency checking. By enabling Swift 6 language mode and strict concurrency checking, Xcode assists in identifying and resolving possible data races at compile time.
Quote from swift.org: “More formally, a data race occurs when one thread accesses memory while the same memory is being modified by another thread. The Swift 6 language mode eliminates these issues by preventing data races at compile time.”
RsyncUI adheres to the new concurrency model of Swift 6.
Swift Concurrency and Asynchronous Execution
Concurrency and asynchronous execution are important parts of Swift. The latest version of Swift simplifies writing asynchronous code using the async and await keywords, as well as the actor protocol. While RsyncUI doesn’t inherently require concurrency, it is automatically introduced through the use of actors, async, and await keywords. The objective is to execute most work synchronously on the main thread, provided it doesn’t block the GUI.
Asynchronous execution can be performed on both the main thread and background threads from the CTP. When executing asynchronous operations on the main thread, it is crucial to use Swift’s structured concurrency, specifically the async/await keywords. The await keyword serves as a suspension point, allowing other and more critical tasks to access the main thread.
Cooperative Thread Pool (CTP)
The following tasks are executed asynchronously on threads from the CTP, adhering to the actor protocol:
- read synchronization tasks from file
- JSON data decoding: asynchronous decoding that inherits the thread from the actor reading data
- JSON data encoding: synchronous encoding on the main thread
- read and sort log records
- preparing output from rsync for display
- preparing data from the log file for display
- checking for updates to RsyncUI
Adhering to the actor protocol, all access to properties within an actor must be performed asynchronously. There are only five actors in RsyncUI, but there are more asynchronous functions, some of which run on the main thread as well.
Structured Concurrency
Some concurrent functions within RsyncUI are structured using async let. You may have several async let statements, and they will all execute in parallel or concurrently. When all async let tasks are completed, the root task or parent task will continue execution.
Structured concurrency might also dictate the order of execution. The keyword await is a suspension point where execution waits until the asynchronous function is completed before continuing. If there are several await statements in sequence, the next one will execute when the current asynchronous task is completed.