Отмена асинхронного метода, вызывающего события

У меня есть безголовое приложение UWP, которое использует внешнюю библиотеку для подключения к последовательному устройству и отправки некоторых команд. Он запускает бесконечный цикл (пока это правда) с 10-минутной паузой между циклами. Процесс измерения занимает около 4 минут. Внешней библиотеке необходимо выполнить 3 измерения, и после каждого она подает сигнал, вызывая событие. Когда событие возникает в четвертый раз, я знаю, что могу вернуть результаты.

Через 4 часа (+/- несколько секунд) библиотека перестает поднимать события (обычно поднимает событие один или два раза, а потом останавливается, ни ошибок, ничего).

Я реализовал в DoMeasureAsync() ниже CancellationTokenSource, который должен был установить свойство IsCancelled в TaskCompletionSource через 8 минут, чтобы задача возвращалась и цикл продолжался.

Проблема: когда измерение не завершается (NMeasureCompletionSource никогда не получает набор результатов в классе CMeasure), задача из nMeasureCompletionSource никогда не отменяется. Делегат, определенный в RespondToCancellationAsync(), должен запуститься через 8 минут.

Если измерение проходит нормально, я вижу в журналах, что код в

taskAtHand.ContinueWith((x) =>

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

вызывается.

Изменить: Возможно ли, что GC приходит через 4 часа и, возможно, освобождает некоторые переменные, из-за чего приложение не может отправлять команды датчику? > - это не так

Что мне здесь не хватает?

 //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();
        }
    }

person Alex Albu    schedule 16.08.2017    source источник
comment
Возможно ли, что GC приходит через 4 часа и, возможно, освобождает некоторые переменные, из-за чего приложение не может отправлять команды на датчик? - Как вы пришли к этой гипотезе? Объекты со строгой ссылкой не будут собираться сборщиком мусора. И даже если бы они это сделали, вы должны были бы получать некоторые исключения нулевого указателя или тому подобное.   -  person Fildor    schedule 16.08.2017
comment
@Fildor в этот момент я могу думать о чем угодно :)   -  person Alex Albu    schedule 16.08.2017
comment
О, этот отчаянный... в таком случае я бы изолировал третью сторону и проверил ее поведение в тестовом проекте. Этот проект не будет делать ничего другого, кроме как начать обработку на устройстве и посмотреть, вызовет ли он обратный вызов. И, возможно, измерить время в оба конца. Если это показывает такое же поведение, я бы связался с производителем и попросил помощи. Если он работает нормально и стабильно, я бы создал макет для использования вместо стороннего, чтобы вы могли лучше отлаживать общий дизайн. О, и облепить его вырубкой. Позже вы можете удалить его снова, но если вы действительно не понимаете, что происходит, пусть код скажет вам.   -  person Fildor    schedule 16.08.2017
comment
@Fildor да, это то, что я пытаюсь сделать сейчас. Снимаем детали и смотрим как себя ведет. У меня есть много журналов, но я удалил их, чтобы было легче увидеть поток здесь.   -  person Alex Albu    schedule 16.08.2017
comment
@AlexAlbu: Ваш код был бы намного проще, если бы вы 1) написали оболочка TAP-over-EAP, 2) заменил все остальные TCS/ ContinueWith код с async/await, и 3) убедиться, что все одноразовые предметы утилизированы (например, CancellationToken.Register).   -  person Stephen Cleary    schedule 16.08.2017
comment
@StephenCleary 1) Да, я тоже хочу на это посмотреть. 2) У меня есть только один ContinuesWith, чтобы убедиться, что CancellationTokenSource удален. 3) Все утилизируется правильно, я провел несколько тестов (пожалуйста, смотрите мой ответ).   -  person Alex Albu    schedule 17.08.2017


Ответы (1)


После дополнительного тестирования я пришел к выводу, что утечки памяти нет и что все объекты удалены. Отмена тоже хорошо работает.

Пока кажется, что моя проблема связана с выполнением безголового приложения на Raspberry Pi. Хотя я использую deferral = taskInstance.GetDeferral(); кажется, что исполнение остановлено в какой-то момент...

Я протестирую больше и вернусь с результатами (возможно, в новом посте, но я также поставлю ссылку здесь).

Изменить: вот новый пост: UWP – приложение Headless прекращает работу через 3–4 часа

Редактировать 2: проблема была связана со сторонней библиотекой, которую мне пришлось использовать, и ее нужно было называть иначе, чем приложение без головы. Внутри он создавал свой собственный TaskScheduler, если SynchronizationContext.Current был нулевым.

person Alex Albu    schedule 17.08.2017