2

I have a headless UWP application that uses an external library to connect to a serial device and send some commands. It runs an infinite loop (while true) with a 10 minute pause between loops. The measurement process takes around 4 minutes. The external library needs to run 3 measurements and after each it signals by raising an event. When the event is raised the 4th time I know that I can return the results.

After 4 hours (+/- a few seconds) the library stops raising events (usually it raises the event one or 2 times and then it halts, no errors, nothing).

I implemented in DoMeasureAsync() below a CancellationTokenSource that was supposed to set the IsCancelled property on the TaskCompletionSource after 8 minutes so that the task returns and the loop continues.

Problem: When the measurement does not complete (the NMeasureCompletionSource never gets its result set in class CMeasure), the task from nMeasureCompletionSource is never cancelled. The delegate defined in RespondToCancellationAsync() should run after the 8 minutes.

If the measurement runs ok, I can see in the logs that the code in the

taskAtHand.ContinueWith((x) =>

        {
            Logger.LogDebug("Disposing CancellationTokenSource...");
            cancellationTokenSource.Dispose();
        });

gets called.

Edit: Is it possible that the GC comes in after the 4 hours and maybe deallocates some variables and doing so makes the app to not be able to send the commands to the sensor? - It is not the case

What am I missing here?

 //this gets called in a while (true) loop
    public Task<PMeasurement> DoMeasureAsync()
    {

        nMeasureCompletionSource = new TaskCompletionSource<PMeasurement>();

        cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(8));

        var t = cMeasure.Run(nitrateMeasureCompletionSource, cancellationTokenSource.Token);

        var taskAtHand = nitrateMeasureCompletionSource.Task;
        taskAtHand.ContinueWith((x) =>

        {
            Logger.LogDebug("Disposing CancellationTokenSource...");
            cancellationTokenSource.Dispose();
        });

        return taskAtHand;
    }

    public class CMeasure
    {
        public async Task Run(TaskCompletionSource<PMeasurement> tcs, CancellationToken cancellationToken)
        {
            try
            {
                NMeasureCompletionSource = tcs;
                CancellationToken = cancellationToken;

                CancellationToken.Register(async () => await RespondToCancellationAsync(), useSynchronizationContext: false);

                CloseDevice(); //Closing device if for some reason is still open
                await Task.Delay(2500);

                TheDevice = await GetDevice();

                measurementsdone = 0;

                Process(); //start the first measurement
            }
            catch (Exception ex)
            {
                DisconnectCommManagerAndCloseDevice();

                NMeasureCompletionSource.SetException(ex);
            }
        }

        public async Task RespondToCancellationAsync()
        {

            if (!NitrateMeasureCompletionSource.Task.IsCompleted)
            {
                Logger.LogDebug("Measure Completion Source is not completed. Cancelling...");
                NMeasureCompletionSource.SetCanceled();
            }

            DisconnectCommManagerAndCloseDevice();

            await Task.Delay(2500);

        }

        private void Process()
        {

            if (measurementsdone < 3)
            {
                var message = Comm.Measure(m); //start a new measurement on the device
            }
            else
            {
                ...
                NMeasureCompletionSource.SetResult(result);
            }

        }

        //the method called when the event is raised by the external library
        private void Comm_EndMeasurement(object sender, EventArgs e)
        {
            measurementsdone++;

            Process();
        }
    }
Alex Albu
  • 693
  • 1
  • 12
  • 20
  • 3
    "Is it possible that the GC comes in after the 4 hours and maybe deallocates some variables and doing so makes the app to not be able to send the commands to the sensor?" - How do you come to that hypothesis? Strongly referenced objects won't be collected by GC. And even if they would, then you should be getting some Null Pointer Exceptions or the like. – Fildor Aug 16 '17 at 07:09
  • @Fildor at this moment i can think at anything :) – Alex Albu Aug 16 '17 at 07:13
  • Oh, *that* desperate ... in that case, I'd isolate the third party and test its behavior in a Test-Project. This project would not do anything else than start processing "on the device" and see if it raises the callback. And maybe measure roundtrip-time. If this shows the same behavior, I'd contact the manufacturer and ask for assistance. If it runs ok and stable then I'd create some mock to use instead off the 3rd party, so you can better debug the overall design. Oh, and plaster it with logging. You can later remove it again but if you really have no idea what happens, let the code tell you. – Fildor Aug 16 '17 at 07:18
  • @Fildor yes, that is something that i am trying to do now. Taking parts out and see how it behaves. I have a lot of logging in place, but I removed it so it is easier to see the flow here – Alex Albu Aug 16 '17 at 07:28
  • @AlexAlbu: Your code would be a lot simpler if you 1) Wrote a [TAP-over-EAP wrapper](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/interop-with-other-asynchronous-patterns-and-types#tasks-and-the-event-based-asynchronous-pattern-eap), 2) replaced all other TCS/`ContinueWith` code with `async`/`await`, and 3) ensured all disposables are disposed (e.g., `CancellationToken.Register`). – Stephen Cleary Aug 16 '17 at 13:44
  • @StephenCleary 1) Yes, it's something I want to look at also. 2) I have only one ContinuesWith to make sure that the CancellationTokenSource gets disposed. 3) Everything gets disposed properly, I ran several tests (please see my answer). – Alex Albu Aug 17 '17 at 08:49

1 Answers1

1

After more testing I have reached the conclusion that there is no memory leak and that all the objects are disposed. The cancellation works well also.

So far it appears that my problem comes from the execution of the headless app on the Raspberry Pi. Although I am using the deferral = taskInstance.GetDeferral(); it seems that the execution is stopped at some point...

I will test more and come back with the results (possibly in a new post, but I will put a link here as well).

Edit: Here is the new post: UWP - Headless app stops after 3 or 4 hours

Edit 2: The problem was from a 3rd party library that I had to use and it had to be called differently from a headless app. Internally it was creating its own TaskScheduler if SynchronizationContext.Current was null.

Alex Albu
  • 693
  • 1
  • 12
  • 20