`DispatchQueue.main.async` seems to get bogged down periodically

Hi everyone,

I have an AR app that allows for collaborative sessions and synchronizes model state (e.g. rotation, can be changed via slider) using Multipeer Connectivity. The receiving peer parses the data and then uses DispatchQueue.main.async to update the UI (SwiftUI) and the model in SceneKit. Lately I have noticed that this synchronization seems to lag periodically.

To analyze the issue better I compiled this minimal reproducible example: https://github.com/MrMuetze/MCSyncTest

The repository includes a boiled down "Multipeer Connectivity" project that should make this issue reproducible on local devices (maybe even between one device and the simulator). I have also added a readme with a gif that shows the issue.

The synchronization between devices worked like a treat for a long time but recently I have noticed that e.g. a rotation is not as smooth as before on the receiving device. A bit of debugging revealed that the messages are received quickly but then the work that needs to happen on the main thread is periodically delayed.

In the example project the relevant code bit that should be executed on the main thread looks like this:

func session(_: MCSession, didReceive data: Data, fromPeer _: MCPeerID) {
    print("received data")
    DispatchQueue.main.async {
        print("doing stuff")
        let doubleVal = data.to(type: Double.self) ?? 0.0
        self.internalSliderValue = doubleVal
        self.sliderValue = doubleVal
    }
}

It updates a published variable sliderValue that is connected to a Slider and a Text UI element.

Regularly (like every 500ms or so) the execution of work on the main thread seems to be delayed. After a short while all outstanding UI updates are executed at once which leads to visual stuttering. This can be observed by looking at the printed messages:

...
received data <-- normal behavior
doing stuff
received data
doing stuff
received data
doing stuff
received data <-- hiccup starts
received data
received data
received data
doing stuff
doing stuff
doing stuff
doing stuff
received data <-- returns to normal behavior
doing stuff
received data
doing stuff
...

I have tried to change the "Quality of Service" to .userInteractive as well as limiting the number of messages that are sent in a certain timeframe (I tried one message every 100ms). Both changes have not helped and even with a much lower number of messages the periodic stuttering remains. Using DispatchQueue.main.sync is also not a solution right now. It does bring the sequence back into original order but the periodic "freeze" of the queue is prevalent there as well. This then leads to a "laggy" execution of what happened on the sending peer device.

I am not very familiar with Profiling an app and using Instruments, but I have captured some timings in regards to the usage of the main thread and some backtraces. From what I can understand the workload of the written code should not be the issue, but rather an underlying system function or library. I can provide more information in regards to the backtraces if needed. Right now I can't really say what would be useful. Below is an image that shows the main thread usage at the very top. This happens when the slider lags as shown in the gif.

I am working with Xcode 15.2 and run the app on iOS 17.3. For devices I use an iPad Pro (2nd gen.) and an iPhone 15 Pro. The issue happens in Debug as well as in Release mode. I can't quite say when the stuttering appeared initially. I wonder if anyone is aware of any changes to iOS or underlying frameworks that could have caused this issue. I am interested in any information about this, if the issue can be resolved or if I have to look for alternative workarounds.

Let me know if I can add any additional information.

Best regards! Bjoern