0

I'm currently developing a Xamarin app that communicates via a Wi-Fi connection with some custom hardware containing a sensor. The job of my app is to retrieve the sensor data from the hardware and display it with oxyplot as a continuous stream of 1-D data. Unfortunately, I noticed that the performance of my app usually degrades over time and at some point it always crashes. The exact crashing time is quite arbitrary but seems to depend on the amount of sensor data (i.e. larger amplitude values result in faster app crashes). Some typical debug output before the app crashes is shown below:

03-07 17:36:54.149 I/art     (10777): Starting a blocking GC Explicit
03-07 17:36:54.164 I/art     (10777): Explicit concurrent mark sweep GC freed 2991(107KB) AllocSpace objects, 0(0B) LOS objects, 39% free, 10MB/17MB, paused 174us total 14.883ms
03-07 17:36:54.168 D/Mono    (10777): GC_TAR_BRIDGE bridges 1104 objects 1104 opaque 0 colors 1104 colors-bridged 1104 colors-visible 1104 xref 0 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.06ms tarjan 0.28ms scc-setup 0.17ms gather-xref 0.01ms xref-setup 0.00ms cleanup 0.14ms
03-07 17:36:54.168 D/Mono    (10777): GC_BRIDGE: Complete, was running for 20.42ms
03-07 17:36:54.168 D/Mono    (10777): GC_MINOR: (Nursery full) time 3.34ms, stw 5.17ms promoted 211K major size: 3664K in use: 2807K los size: 14336K in use: 11300K
03-07 17:36:56.248 I/art     (10777): Starting a blocking GC Explicit
03-07 17:36:56.263 I/art     (10777): Explicit concurrent mark sweep GC freed 3093(111KB) AllocSpace objects, 0(0B) LOS objects, 39% free, 10MB/17MB, paused 180us total 14.729ms
03-07 17:36:56.267 D/Mono    (10777): GC_TAR_BRIDGE bridges 1144 objects 1144 opaque 0 colors 1144 colors-bridged 1144 colors-visible 1144 xref 0 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.07ms tarjan 0.30ms scc-setup 0.18ms gather-xref 0.01ms xref-setup 0.00ms cleanup 0.13ms
03-07 17:36:56.267 D/Mono    (10777): GC_BRIDGE: Complete, was running for 21.07ms
03-07 17:36:56.267 D/Mono    (10777): GC_MINOR: (Nursery full) time 3.60ms, stw 4.38ms promoted 216K major size: 3904K in use: 3055K los size: 18432K in use: 14810K
03-07 17:36:56.962 D/Mono    (10777): GC_BRIDGE waiting for bridge processing to finish
03-07 17:36:56.968 I/art     (10777): Starting a blocking GC Explicit
03-07 17:36:56.988 I/art     (10777): Explicit concurrent mark sweep GC freed 1327(43KB) AllocSpace objects, 0(0B) LOS objects, 40% free, 10MB/17MB, paused 220us total 19.794ms
03-07 17:36:56.992 D/Mono    (10777): GC_TAR_BRIDGE bridges 648 objects 649 opaque 0 colors 648 colors-bridged 648 colors-visible 648 xref 0 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.11ms tarjan 0.29ms scc-setup 0.16ms gather-xref 0.01ms xref-setup 0.00ms cleanup 0.13ms
03-07 17:36:56.992 D/Mono    (10777): GC_BRIDGE: Complete, was running for 25.65ms
03-07 17:36:56.992 D/Mono    (10777): GC_MAJOR: (LOS overflow) time 16.50ms, stw 21.50ms los size: 5120K in use: 460K
03-07 17:36:56.992 D/Mono    (10777): GC_MAJOR_SWEEP: major size: 3984K in use: 2657K
03-07 17:36:59.675 I/Choreographer(10777): Skipped 116 frames!  The application may be doing too much work on its main thread.
03-07 17:37:01.298 I/art     (10777): Starting a blocking GC Explicit
03-07 17:37:01.304 D/Mono    (10777): GC_BRIDGE waiting for bridge processing to finish
03-07 17:37:01.314 I/art     (10777): Explicit concurrent mark sweep GC freed 3078(111KB) AllocSpace objects, 0(0B) LOS objects, 40% free, 10MB/17MB, paused 173us total 15.393ms
03-07 17:37:01.319 D/Mono    (10777): GC_TAR_BRIDGE bridges 1144 objects 1144 opaque 0 colors 1144 colors-bridged 1144 colors-visible 1144 xref 0 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.07ms tarjan 0.33ms scc-setup 0.20ms gather-xref 0.02ms xref-setup 0.00ms cleanup 0.40ms
03-07 17:37:01.319 D/Mono    (10777): GC_BRIDGE: Complete, was running for 23.28ms
03-07 17:37:01.319 D/Mono    (10777): GC_MINOR: (Nursery full) time 3.76ms, stw 5.02ms promoted 216K major size: 4128K in use: 2905K los size: 5120K in use: 4050K
03-07 17:37:03.143 I/art     (10777): Starting a blocking GC Explicit
03-07 17:37:03.158 I/art     (10777): Explicit concurrent mark sweep GC freed 3070(110KB) AllocSpace objects, 0(0B) LOS objects, 40% free, 10MB/17MB, paused 252us total 14.860ms
03-07 17:37:03.161 D/Mono    (10777): GC_TAR_BRIDGE bridges 1134 objects 1134 opaque 0 colors 1134 colors-bridged 1134 colors-visible 1134 xref 0 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.07ms tarjan 0.30ms scc-setup 0.18ms gather-xref 0.01ms xref-setup 0.00ms cleanup 0.13ms
03-07 17:37:03.161 D/Mono    (10777): GC_BRIDGE: Complete, was running for 20.60ms
03-07 17:37:03.161 D/Mono    (10777): GC_MINOR: (Nursery full) time 3.40ms, stw 4.31ms promoted 216K major size: 4256K in use: 3153K los size: 9216K in use: 7680K
03-07 17:37:05.200 I/art     (10777): Starting a blocking GC Explicit
03-07 17:37:05.216 I/art     (10777): Explicit concurrent mark sweep GC freed 3008(108KB) AllocSpace objects, 0(0B) LOS objects, 39% free, 10MB/17MB, paused 182us total 15.827ms
03-07 17:37:05.219 D/Mono    (10777): GC_TAR_BRIDGE bridges 1110 objects 1110 opaque 0 colors 1110 colors-bridged 1110 colors-visible 1110 xref 0 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.07ms tarjan 0.32ms scc-setup 0.19ms gather-xref 0.01ms xref-setup 0.00ms cleanup 0.16ms
03-07 17:37:05.219 D/Mono    (10777): GC_BRIDGE: Complete, was running for 21.94ms
03-07 17:37:05.219 D/Mono    (10777): GC_MINOR: (Nursery full) time 3.70ms, stw 4.93ms promoted 208K major size: 4416K in use: 3393K los size: 13312K in use: 11258K
Thread finished: <Thread Pool> #4
Thread finished: <Thread Pool> #2
Thread started: <Thread Pool> #10
Thread finished: <Thread Pool> #3
Thread finished:  #11

This is the method containing the main loop in which I update the plot:

public static async Task MainMethodAsync(MyDisplay MyDisplayPlot, DateTime StartTime, MainPage mainPage) {
    ToggleAccelerometer();
    Accelerometer.ReadingChanged += Accelerometer_ReadingChanged;
    Client = new MyClientSocket();
    await Client.ConnectAndStartReadTaskAsync();
    int FrameCounter = 0;
    while (ShootContinuously) {
        FrameCounter++;
        await GetDataAndUpdateUIAsync(MyDisplayPlot,FrameCounter,StartTime,mainPage);
    }
    await Client.DisconnectAndStopReadTaskAsync();
}

The method getting called in every iteration is listed below:

private static async Task GetDataAndUpdateUIAsync(MyDisplay MyDisplayPlot, int FrameCounter, DateTime StartTime, MainPage mainPage) {
    ShootAsync();
    await ReceiveAsync();
    if (PlottingEnabledFlag) {
        MyDisplayPlot.UpdateMyDisplayPlot();
    }
    DateTime CurrentTime = DateTime.Now;
    double elapsedSecs = (CurrentTime - StartTime).TotalSeconds;
    mainPage.Title = "Frames: " + FramesCounter.ToString() + ", Frames/s: " + System.Math.Round((FramesCounter / elapsedSecs), 3).ToString();
}

What have I tried already?

  1. My first guess was that oxyplot might be the culprit, which is why I introduced a flag with which I can disable the plotting of my data. Unfortunately, the app still crashes at some point even if no data is being plotted at all.
  2. On StackOverflow I read that "Skipped XX frames! The application may be doing too much work on its main thread" might be the problem. Thus, I decided to use the RunOnUiThread method but that just resulted in the data not being processed at all.
  3. Another idea was to introduce a timeout for the async method. That works okayish but has the disadvantage that after a while the timeout threshold is hit in every iteration and the app doesn't seem to return to "business as usual".

Any additional ideas are warmly welcome and any input is highly appreciated. Thank you.

Update

This is how the main method looks after introducing some timeout and cleanup logic:

    [...]
    int timeout = 1000;
    while (ShootContinuously) {
        FrameCounter++;
        CancellationToken cancellationToken = new CancellationToken();
        var task = GetDataAndUpdateUIForContinuousShootingAsync(MyDisplayPlot, FrameCounter, StartTime, mainPage);
        if (await Task.WhenAny(task, Task.Delay(timeout, cancellationToken)) == task) {
            // Task completed within timeout.
            // Consider that the task may have faulted or been canceled.
            // We re-await the task so that any exceptions/cancellation is rethrown.
            await task;
        } else { // timeout/cancellation logic
            Debug.WriteLine("Task is taking too long!");
            await Client.DisconnectAndStopReadTaskAsync();
            Client = null;
            GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Client = new MyClientSocket();
            await Client.ConnectAndStartReadTaskAsync();
            continue;
        }
    }
    await Client.DisconnectAndStopReadTaskAsync();
Hagbard
  • 3,430
  • 5
  • 28
  • 64
  • have you looked at your usage and management of memory? – Jason Mar 07 '19 at 17:05
  • I'm still relatively new to Xamarin. What would be the best way to do that? – Hagbard Mar 07 '19 at 17:12
  • 1
    https://learn.microsoft.com/en-us/xamarin/tools/profiler/?tabs=macos – Jason Mar 07 '19 at 17:16
  • Try using the profiler if you ask me – FreakyAli Mar 07 '19 at 18:45
  • What is the frame rate of your `while (ShootContinuously) {` loop? – SushiHangover Mar 07 '19 at 22:37
  • Thank you for your input. I'll give the profiler a try. @SushiHangover: The frame rate starts at roughly 2 frames/s and continuously increases until it reaches around 10 frames/s at maximum. After that, the app usually crashes. – Hagbard Mar 08 '19 at 08:57
  • Unfortunately, the Xamarin profiler doesn't seem to be available unless you have the Visual Studio Enterprise version, which I don't. Very sad that such an essential tool isn't available in the community edition. – Hagbard Mar 08 '19 at 09:48
  • 1
    It does not surprise me that you are experiencing GC "overload" crashes. Updating the Android UI via C# strings is very costly within the Xamarin.Android framework, and do so in a continuous loop is deadly (plus I'm assuming your plotting software is GC heavy also on update). – SushiHangover Mar 08 '19 at 17:23
  • If I completely disable all plotting of my data and comment out the continuous updating of the UI with a string, the "skipping XXX frames" messages are gone indeed. However, my app still crashes after a while with the method in question always hitting the timeout threshold (set to 1s). – Hagbard Mar 08 '19 at 17:40
  • I updated my post to include the new method with some timeout / cleanup logic. – Hagbard Mar 08 '19 at 17:53
  • 1
    Sensors typically are able to generate an ungodly amount of data in a short period of time, capable of overwhelming any application/hardware. If you don't have a strategy to manage data volume by filtering it and/or discarding older data, you're asking for disaster, even more when you have a chart in the loop. – jsanalytics Mar 09 '19 at 08:34

0 Answers0