Расширенное выполнение не работает должным образом?

Я не могу заставить ExtendedExecution работать должным образом. Проблема в том, что событие Revoked не запускается до завершения выполнения. Если взять образец:

private async void OnSuspending(object sender, SuspendingEventArgs e)
{
    Debug.WriteLine("Suspending in app");
    var deferral = e.SuspendingOperation.GetDeferral();
    using (var session = new ExtendedExecutionSession())
    {
        session.Reason = ExtendedExecutionReason.SavingData;
        session.Description = "Upload Data";
        session.Revoked += (s, a) => { Debug.WriteLine($"Extended execution revoked because of {a.Reason}"); };
        var result = await session.RequestExtensionAsync();
        if (result == ExtendedExecutionResult.Denied) Debug.WriteLine("Extended execution failed");
        else
        {
            Debug.WriteLine("Executing");
            await Task.Run(() => { Task.Delay(9000).Wait(); Debug.WriteLine("Finished the task"); });
            Debug.WriteLine("Executing finished");
        }
        Debug.WriteLine("Suspending after execution");
    }
    deferral.Complete();
}

В документации указано, что событие Revoked должно запускаться при возобновлении работы приложения, но если вы попробуете код с подключенным отладчиком, вы увидите, что вывод отладки выглядит нормально, но вам нужно подождать 9000 мс, чтобы он появился. Это означает, что код приостанавливается до завершения сеанса.

Самая большая проблема заключается в том, что если вы запустите это без подключенного отладчика, запустите приложение, приостановите, а затем возобновите работу, вы увидите черный экран на несколько секунд, а затем ОС завершит работу вашего приложения.

Я что-то упускаю? У кого-нибудь он работает корректно?


person Romasz    schedule 19.03.2016    source источник
comment
Похоже, вы ничего не упускаете, но я не могу найти нигде в документации, где говорится, что событие Revoked должно запускаться при возобновлении работы приложения. Возможно, вам нужно добавить дополнительный код в OnResuming.   -  person Mehrzad Chehraz    schedule 23.03.2016
comment
@MehrzadChehraz Нет - событие возобновления, похоже, запускается только после завершения события приостановки. Это означает, что в режиме выпуска приложение будет уничтожено ОС из-за некоторых ограничений по времени и вообще не будет поднимать отозванные и возобновленные события.   -  person Romasz    schedule 23.03.2016
comment
Да, ты прав. Кстати, можете ли вы добавить ссылку или что-то в документацию, в которой говорится, что событие Revoked должно запускаться при возобновлении работы приложения?   -  person Mehrzad Chehraz    schedule 23.03.2016
comment
@MehrzadChehraz Я беру это от здесь, в MSDN - одним из двух ответов является возобновление работы приложения, что имеет смысл - например, для передачи сигнала отмены или запуска другого метода возобновления. Поскольку я протестировал приведенный выше код, порядок вывода отладки правильный - если вы сразу запускаете приостановку и возобновление, подождите несколько секунд, вы увидите, что отмененное событие запускается до «Выполнение завершено». Проблема в том, что каким-то образом вы увидите результат только после того, как все закончится.   -  person Romasz    schedule 23.03.2016
comment
@MehrzadChehraz В режиме выпуска, когда PLM отключен, у приложения не так много времени, поэтому оно будет закрыто. Я могу просто подозревать, что это связано с тем, что поток пользовательского интерфейса занят ожиданием в расширенном сеансе выполнения. Но ИМХО, в данном случае вообще не имеет смысла использовать ExtendedExecutionSession - если есть большая вероятность того, что приложение рухнет, как только пользователь вернется к нему - более того, вы не можете сказать в в какой момент произойдет сбой - например в середине сохранения файла. Поэтому я ищу то, что я пропустил, может быть, я просто думаю неправильно.   -  person Romasz    schedule 23.03.2016
comment
Я думал то же самое о UI Thread, но я не думаю, что это так. Если он будет чего-то ждать, то какой смысл в мероприятии? Единственное, что нам не хватает, — это надежная платформа для работы. В итоге я вообще не полагался на OnSuspending.   -  person Mehrzad Chehraz    schedule 23.03.2016
comment
@MehrzadChehraz Как я думаю, событие приостановки является одним из самых важных - я не могу представить, как мое приложение будет работать без такого события - отправить сигнал отмены, завершить сохранение и многое другое.   -  person Romasz    schedule 23.03.2016
comment
Я знаю, это смешно, сохранение данных должно выполняться с интервалами во время работы приложения, я думаю, что легкий сигнал отмены (менее 5 секунд) должен быть в порядке, чтобы быть в приостановке.   -  person Mehrzad Chehraz    schedule 23.03.2016
comment
Возможная проблема: вы не завершаете отсрочку от Suspending до тех пор, пока все не будет сделано. Сможете ли вы завершить, как только получите расширенное выполнение (перед выполнением ожидания)?   -  person Peter Torr - MSFT    schedule 30.03.2016
comment
@PeterTorr-MSFT Как я проверял, это помогает в одном: приложение возобновляет работу и не падает без отладчика. Но есть пара проблем - отозванное событие в этом случае вообще не вызывается, и есть опасность, что пользователь приостановит работу во второй раз, после чего приложение выдаст исключение. Технически я, вероятно, могу передать сигнал отмены из события возобновления, но я не уверен в этом - некоторые ресурсы, вероятно, освобождаются после deferal.Complete() (я полагаю, что от этого не исходит отмененное событие), и поэтому я не могу сказать, если У меня будет такая же ссылка на токен отмены при возобновлении.   -  person Romasz    schedule 30.03.2016


Ответы (2)


Использование await и Task приводит к тому, что ваша продолжающаяся задача остается в основном потоке, из-за чего вам приходится ждать с черным экраном. Помните, что поведение await состоит в том, чтобы запланировать выполнение в Dispatcher, НЕ начать новый поток, НИ запланировать его выполнение в ThreadPool. Как следствие, никакие другие сообщения пользовательского интерфейса не могут быть обработаны, пока не закончится Delay().

Просто выполните трудоемкую операцию в новом потоке, но убедитесь, что сеанс остается открытым до его завершения.

Взгляните на это https://msdn.microsoft.com/en-us/magazine/jj991977.aspx, чтобы получить представление о том, как планируется выполнение.

person Jairo Andres Velasco Romero    schedule 29.03.2016
comment
Любые идеи, как выполнить трудоемкую операцию в новом потоке и обеспечить сохранение сеанса открытым до его завершения без использования await в потоке пользовательского интерфейса? - person Romasz; 30.03.2016

Нет никаких проблем с пользовательским интерфейсом или что-то в этом роде. Ваш код работает. Ваше ожидание ошибочно.

Используя ExtendedExecutionSession, вы сообщаете своему приложению, что вам нужно время для сохранения, и оно не будет отозвано, пока вы не закончите. В вашем случае это занимает около 9 секунд.

Попробуйте приостановить приложение, подождите 10 секунд, а затем отзовите его. Это произойдет немедленно. Затем попробуйте приостановить приложение и отозвать его до завершения сеанса. Теперь ExtendedExecutionSession сообщит вашей ОС, что ваше приложение еще не может быть отозвано, и оно должно дождаться завершения процесса сохранения. Это то, что ты хочешь.

См. документ Microsoft при расширенном выполнении:

Запрос расширенного сеанса выполнения ExtendedExecutionReason.SavingData, когда приложение находится в состоянии «Приостановка», создает потенциальную проблему, о которой вам следует знать. Если расширенный сеанс выполнения запрашивается в состоянии «Приостановка», а пользователь запрашивает повторный запуск приложения, может показаться, что запуск занимает много времени. Это связано с тем, что период времени расширенного сеанса выполнения должен завершиться до закрытия старого экземпляра приложения и запуска нового экземпляра приложения. Время запуска приносится в жертву, чтобы гарантировать, что пользовательское состояние не будет потеряно.

То, что сказано в разделе про "Revoke", вам тоже интересно:

Когда событие Revoked запускается для расширенного сеанса выполнения ExtendedExecutionReason.SavingData, у приложения есть одна секунда, чтобы завершить операцию, которую оно выполняло, и завершить приостановку.

Одной секунды недостаточно, чтобы закончить 9-секундное ожидание.

Чтобы исключить возможность отложенного отображения вашего отладочного вывода, вы можете протестировать его, добавив в вывод текущее время. У ОС, вероятно, есть проблема с тем, что сеанс не закрывается должным образом, потому что 9 секунд не заканчиваются.

Также обратите внимание на примечание к EnterBackground. :

Ранее приостановка обратного вызова была лучшим местом для сохранения состояния после того, как пользователь завершил сеанс работы с вашим приложением. Однако теперь приложение может продолжить работу в фоновом режиме, а затем вернуться на передний план из-за активности триггера, даже не достигнув состояния приостановки. Лучшее место для сохранения данных после сеанса пользователя — во введенном вами обработчике фоновых событий.

Вы, вероятно, захотите сделать свой код на случай, если будет запущено событие Exiting.

Для OnSuspending попробуйте выполнить ожидание с циклом for, который прерывается (отменяет процесс сохранения), как только происходит отзыв, ожидая только полсекунды за раз.

ОБНОВИТЬ:

...Или используйте фоновую задачу, так как надежное предупреждение перед завершением:

//
// Declare that your background task's Run method makes asynchronous calls by
// using the async keyword.
//
public async void Run(IBackgroundTaskInstance taskInstance)
{
    //
    // Create the deferral by requesting it from the task instance.
    //
    BackgroundTaskDeferral deferral = taskInstance.GetDeferral();

    //
    // Call asynchronous method(s) using the await keyword.
    //
    var result = await ExampleMethodAsync();

    //
    // Once the asynchronous method(s) are done, close the deferral.
    //
    deferral.Complete();
}

ОБНОВЛЕНИЕ 2:

Чтобы узнать, как это сделать правильно, см. официальный пример:

private async void OnSuspending(object sender, SuspendingEventArgs args)
{
    suspendDeferral = args.SuspendingOperation.GetDeferral();

    rootPage.NotifyUser("", NotifyType.StatusMessage);

    using (var session = new ExtendedExecutionSession())
    {
        session.Reason = ExtendedExecutionReason.SavingData;
        session.Description = "Pretending to save data to slow storage.";
        session.Revoked += ExtendedExecutionSessionRevoked;

        ExtendedExecutionResult result = await session.RequestExtensionAsync();
        switch (result)
        {
            case ExtendedExecutionResult.Allowed:
                // We can perform a longer save operation (e.g., upload to the cloud).
                try
                {
                    MainPage.DisplayToast("Performing a long save operation.");
                    cancellationTokenSource = new CancellationTokenSource();
                    await Task.Delay(TimeSpan.FromSeconds(10), cancellationTokenSource.Token);
                    MainPage.DisplayToast("Still saving.");
                    await Task.Delay(TimeSpan.FromSeconds(10), cancellationTokenSource.Token);
                    MainPage.DisplayToast("Long save complete.");
                }
                catch (TaskCanceledException) { }
                break;
            default:
            case ExtendedExecutionResult.Denied:
                // We must perform a fast save operation.
                MainPage.DisplayToast("Performing a fast save operation.");
                await Task.Delay(TimeSpan.FromSeconds(1));
                MainPage.DisplayToast("Fast save complete.");
                break;
        }

        session.Revoked -= ExtendedExecutionSessionRevoked;
    }

    suspendDeferral?.Complete();
    suspendDeferral = null;
}

private async void ExtendedExecutionSessionRevoked(object sender, ExtendedExecutionRevokedEventArgs args)
{
    //If session is revoked, make the OnSuspending event handler stop or the application will be terminated
    if (cancellationTokenSource != null){ cancellationTokenSource.Cancel(); }

    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        switch (args.Reason)
        {
            case ExtendedExecutionRevokedReason.Resumed:
                // A resumed app has returned to the foreground
                rootPage.NotifyUser("Extended execution revoked due to returning to foreground.", NotifyType.StatusMessage);
                break;

            case ExtendedExecutionRevokedReason.SystemPolicy:
                //An app can be in the foreground or background when a revocation due to system policy occurs
                MainPage.DisplayToast("Extended execution revoked due to system policy.");
                rootPage.NotifyUser("Extended execution revoked due to system policy.", NotifyType.StatusMessage);
                break;
        }

        suspendDeferral?.Complete();
        suspendDeferral = null;
    });
}
person Neepsnikeep    schedule 10.08.2017
comment
Я все еще не вижу использования Revoked для себя - пока я могу использовать метод Resuming, LeavingBackground, Launching. Я ожидаю, что отозванное событие будет выполняться в отдельном потоке, поэтому я могу передать токены отмены своей основной задаче или что-то сделать. На данный момент он работает в том же потоке и блокируется до завершения расширенного выполнения. - person Romasz; 10.08.2017
comment
Это означает, что вы должны использовать расширенное выполнение в другом потоке и не блокировать метод Suspending. Поскольку Suspending ожидает, что вы закончите менее чем за несколько секунд, а вам нужно больше времени, я думаю, это может помочь. Я бы начал фоновую задачу с приостановки и использовал там расширенное выполнение. Таким образом, он всегда (пытается) завершить сохранение, независимо от того, отозвали вы свое приложение или нет во время процесса сохранения. Если данные были повреждены во время работы приложения, вам следует прервать процесс сохранения в Revoked. - person Neepsnikeep; 17.08.2017
comment
Фоновая задача - это другой способ, и она не имеет ничего общего с расширенным выполнением, как я думаю. Обратите внимание, что мой образец уже работает в новом потоке, что не должно блокировать работу обычного пользовательского интерфейса. - person Romasz; 17.08.2017
comment
Вы правы, что он не должен блокировать ваш поток пользовательского интерфейса. Но это блокирует поток, в котором запускаются события приостановки и отмены. Таким образом, ваша проблема, которая была отозвана, ожидает завершения приостановки. Поэтому я предлагаю вам запустить фоновую задачу и не блокировать приостановку, чтобы отозванное событие могло срабатывать, как ожидалось. Я думаю, вы правы в том, что вам не нужно расширенное выполнение в фоновом потоке... Ах да, конечно, вы правы, расширенное выполнение - это не то, что вы хотите, поскольку вы ожидаете, что ваше приложение будет реагировать немедленно, независимо от того если процесс сохранения все еще запущен или нет. - person Neepsnikeep; 17.08.2017
comment
Вот почему я думаю, что это «не работает должным образом» - вы запускаете что-то в фоновом потоке, ожидаете отзывчивого пользовательского интерфейса, но это не так, поэтому приостановка/возобновление ожидает завершения вашего фонового потока, тогда как он должен просто нормально работать в пользовательском интерфейсе нить (как я думаю). - person Romasz; 17.08.2017
comment
Но это именно то, что вы говорите своему приложению для расширенного выполнения. Вы сообщаете своему приложению, что приостановленное состояние должно сохраняться до тех пор, пока ваш материал SavingData не будет завершен. Нет пользовательского интерфейса в приостановленном состоянии. Таким образом, ваше приложение не отвечает до тех пор, пока процесс сохранения 9 секунд не будет прерван, потому что для завершения требуется больше одной секунды. Даже если вам не было отказано в расширенном исполнении. - person Neepsnikeep; 17.08.2017
comment
Хорошо, но я ожидаю, что событие revoked сработает в незаблокированном потоке пользовательского интерфейса и даст возможность как-то отреагировать. Без этого мне действительно не нужно это событие (за исключением некоторых очень редких случаев), поэтому все завершается приостановкой, и я могу использовать событие возобновления для всего остального. - person Romasz; 17.08.2017
comment
Вот почему я предложил выполнять расширенный сеанс выполнения в фоновой задаче, чтобы вы могли получить отозванное событие. Что вы можете попробовать, так это сделать обработчик отозванных событий асинхронным и посмотреть, достаточно ли этого для своевременного выполнения. - person Neepsnikeep; 17.08.2017
comment
Как бы вы перенесли расширенное выполнение на фоновую задачу? Вы имеете в виду IBackgroundTask? (это другой процесс) - person Romasz; 17.08.2017
comment
Да, я имею в виду регистрацию IBackgroundTask и, таким образом, избежание обычного способа приостановки и отзыва приложения. Это не то, как это должно быть сделано, но я действительно думаю, что это то, что вы хотите, чтобы произошло. - person Neepsnikeep; 17.08.2017