Gestire un'attività in background annullata

API importanti

Informazioni su come creare un'attività in background che riconosca una richiesta di annullamento, interrompa il lavoro e segnali l'annullamento all'app usando l'archiviazione permanente.

In questo argomento si presuppone che sia già stata creata una classe di attività in background, incluso il metodo Run usato come punto di ingresso dell'attività in background. Per iniziare a costruire rapidamente un background, vedere Creare e registrare un'attività in background out-of-process o Creare e registrare un'attività in background in-process se è prima necessario creare un'attività in background. Per informazioni più approfondite su condizioni e trigger, vedere Supportare l'app con le attività in background.

Questo argomento è applicabile anche alle attività in background in-process. Invece del metodo Run, sostituire OnBackgroundActivated. Le attività in background in-process non richiedono l'uso dell'archiviazione permanente per segnalare l'annullamento perché è possibile comunicare l'annullamento usando lo stato dell'app perché l'attività in background è in esecuzione nello stesso processo dell'app in primo piano.

Usare il metodo OnCanceled per riconoscere le richieste di annullamento

Scrivere un metodo per gestire l'evento di annullamento.

Nota

Per tutte le famiglie di dispositivi ad eccezione del desktop, se la memoria del dispositivo diventa insufficiente, le attività in background potrebbero essere terminate. Se non viene rilevata un'eccezione memoria insufficiente o l'app non la gestisce, l'attività in background verrà terminata senza avviso e senza generare l'evento OnCanceled. Ciò consente di garantire l'esperienza utente dell'app in primo piano. L'attività in background deve essere progettata per gestire questo scenario.

Creare un metodo denominato OnCanceled come indicato di seguito. Questo metodo è il punto di ingresso chiamato da Windows Runtime quando viene effettuata una richiesta di annullamento sull'attività in background.

private void OnCanceled(
    IBackgroundTaskInstance sender,
    BackgroundTaskCancellationReason reason)
{
    // TODO: Add code to notify the background task that it is cancelled.
}
void ExampleBackgroundTask::OnCanceled(
    Windows::ApplicationModel::Background::IBackgroundTaskInstance const& taskInstance,
    Windows::ApplicationModel::Background::BackgroundTaskCancellationReason reason)
{
    // TODO: Add code to notify the background task that it is cancelled.
}
void ExampleBackgroundTask::OnCanceled(
    IBackgroundTaskInstance^ taskInstance,
    BackgroundTaskCancellationReason reason)
{
    // TODO: Add code to notify the background task that it is cancelled.
}

Aggiungere una variabile flag denominata _CancelRequested alla classe dell'attività in background. Questa variabile verrà utilizzata per indicare quando è stata effettuata una richiesta di annullamento.

volatile bool _CancelRequested = false;
private:
    volatile bool m_cancelRequested;
private:
    volatile bool CancelRequested;

Nel metodo OnCanceled creato nel passaggio 1 impostare la variabile flag _CancelRequested su true.

L'esempio completo di attività in background con metodoOnCanceled imposta _CancelRequested su true e scrive un output di debug potenzialmente utile.

private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
    // Indicate that the background task is canceled.
    _cancelRequested = true;

    Debug.WriteLine("Background " + sender.Task.Name + " Cancel Requested...");
}
void ExampleBackgroundTask::OnCanceled(
    Windows::ApplicationModel::Background::IBackgroundTaskInstance const& taskInstance,
    Windows::ApplicationModel::Background::BackgroundTaskCancellationReason reason)
{
    // Indicate that the background task is canceled.
    m_cancelRequested = true;
}
void ExampleBackgroundTask::OnCanceled(IBackgroundTaskInstance^ taskInstance, BackgroundTaskCancellationReason reason)
{
    // Indicate that the background task is canceled.
    CancelRequested = true;
}

Nel metodo Run dell'attività in background registrare il metodo del gestore eventi OnCanceled prima di iniziare il lavoro. In un'attività in background in-process è possibile eseguire questa registrazione come parte dell'inizializzazione dell'applicazione. Ad esempio, usare la seguente riga di codice.

taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);
taskInstance.Canceled({ this, &ExampleBackgroundTask::OnCanceled });
taskInstance->Canceled += ref new BackgroundTaskCanceledEventHandler(this, &ExampleBackgroundTask::OnCanceled);

Gestire l'annullamento chiudendo l'attività in background

Quando viene ricevuta una richiesta di annullamento, il metodo che esegue operazioni in background deve arrestare il lavoro e uscire riconoscendo quando _cancelRequested è impostato su true. Per le attività in background in-process, ciò significa che viene restituito dal metodo OnBackgroundActivated. Per le attività in background out-of-process, ciò significa restituire dal metodo Run.

Modificare il codice della classe di attività in background per controllare la variabile flag mentre funziona. Se _cancelRequested diventa impostato su true, interrompere il lavoro da continuare.

L'esempio di attività in background include un controllo che arresta il callback timer periodico se l'attività in background viene annullata.

if ((_cancelRequested == false) && (_progress < 100))
{
    _progress += 10;
    _taskInstance.Progress = _progress;
}
else
{
    _periodicTimer.Cancel();
    // TODO: Record whether the task completed or was cancelled.
}
if (!m_cancelRequested && m_progress < 100)
{
    m_progress += 10;
    m_taskInstance.Progress(m_progress);
}
else
{
    m_periodicTimer.Cancel();
    // TODO: Record whether the task completed or was cancelled.
}
if ((CancelRequested == false) && (Progress < 100))
{
    Progress += 10;
    TaskInstance->Progress = Progress;
}
else
{
    PeriodicTimer->Cancel();
    // TODO: Record whether the task completed or was cancelled.
}

Nota

L'esempio di codice illustrato in precedenza usa la proprietà IBackgroundTaskInstance.Progress utilizzata per registrare lo stato dell'attività in background. Lo stato di avanzamento viene riportato all'app usando la classe BackgroundTaskProgressEventArgs.

Modificare il metodo Run in modo che dopo che il lavoro sia stato arrestato, registra se l'attività è stata completata o annullata. Questo passaggio si applica alle attività in background out-of-process perché è necessario un modo per comunicare tra processi quando l'attività in background è stata annullata. Per le attività in background in-process, è sufficiente condividere lo stato con l'applicazione per indicare che l'attività è stata annullata.

L'esempio di attività in background registra lo stato in Local Impostazioni.

if ((_cancelRequested == false) && (_progress < 100))
{
    _progress += 10;
    _taskInstance.Progress = _progress;
}
else
{
    _periodicTimer.Cancel();

    var settings = ApplicationData.Current.LocalSettings;
    var key = _taskInstance.Task.TaskId.ToString();

    // Write to LocalSettings to indicate that this background task ran.
    if (_cancelRequested)
    {
        settings.Values[key] = "Canceled";
    }
    else
    {
        settings.Values[key] = "Completed";
    }
        
    Debug.WriteLine("Background " + _taskInstance.Task.Name + (_cancelRequested ? " Canceled" : " Completed"));
        
    // Indicate that the background task has completed.
    _deferral.Complete();
}
if (!m_cancelRequested && m_progress < 100)
{
    m_progress += 10;
    m_taskInstance.Progress(m_progress);
}
else
{
    m_periodicTimer.Cancel();

    // Write to LocalSettings to indicate that this background task ran.
    auto settings{ Windows::Storage::ApplicationData::Current().LocalSettings() };
    auto key{ m_taskInstance.Task().Name() };
    settings.Values().Insert(key, (m_progress < 100) ? winrt::box_value(L"Canceled") : winrt::box_value(L"Completed"));

    // Indicate that the background task has completed.
    m_deferral.Complete();
}
if ((CancelRequested == false) && (Progress < 100))
{
    Progress += 10;
    TaskInstance->Progress = Progress;
}
else
{
    PeriodicTimer->Cancel();
        
    // Write to LocalSettings to indicate that this background task ran.
    auto settings = ApplicationData::Current->LocalSettings;
    auto key = TaskInstance->Task->Name;
    settings->Values->Insert(key, (Progress < 100) ? "Canceled" : "Completed");
        
    // Indicate that the background task has completed.
    Deferral->Complete();
}

Osservazioni:

È possibile scaricare l'esempio di attività in background per visualizzare questi esempi di codice nel contesto dei metodi.

A scopo illustrativo, il codice di esempio mostra solo parti del metodo Run (e del timer di callback) dell'esempio di attività in background.

Esempio di metodo Run

Il metodo Run completo e il codice di callback timer dell'esempio di attività in background sono illustrati di seguito per il contesto.

// The Run method is the entry point of a background task.
public void Run(IBackgroundTaskInstance taskInstance)
{
    Debug.WriteLine("Background " + taskInstance.Task.Name + " Starting...");

    // Query BackgroundWorkCost
    // Guidance: If BackgroundWorkCost is high, then perform only the minimum amount
    // of work in the background task and return immediately.
    var cost = BackgroundWorkCost.CurrentBackgroundWorkCost;
    var settings = ApplicationData.Current.LocalSettings;
    settings.Values["BackgroundWorkCost"] = cost.ToString();

    // Associate a cancellation handler with the background task.
    taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);

    // Get the deferral object from the task instance, and take a reference to the taskInstance;
    _deferral = taskInstance.GetDeferral();
    _taskInstance = taskInstance;

    _periodicTimer = ThreadPoolTimer.CreatePeriodicTimer(new TimerElapsedHandler(PeriodicTimerCallback), TimeSpan.FromSeconds(1));
}

// Simulate the background task activity.
private void PeriodicTimerCallback(ThreadPoolTimer timer)
{
    if ((_cancelRequested == false) && (_progress < 100))
    {
        _progress += 10;
        _taskInstance.Progress = _progress;
    }
    else
    {
        _periodicTimer.Cancel();

        var settings = ApplicationData.Current.LocalSettings;
        var key = _taskInstance.Task.Name;

        // Write to LocalSettings to indicate that this background task ran.
        settings.Values[key] = (_progress < 100) ? "Canceled with reason: " + _cancelReason.ToString() : "Completed";
        Debug.WriteLine("Background " + _taskInstance.Task.Name + settings.Values[key]);

        // Indicate that the background task has completed.
        _deferral.Complete();
    }
}
void ExampleBackgroundTask::Run(Windows::ApplicationModel::Background::IBackgroundTaskInstance const& taskInstance)
{
    // Query BackgroundWorkCost
    // Guidance: If BackgroundWorkCost is high, then perform only the minimum amount
    // of work in the background task and return immediately.
    auto cost{ Windows::ApplicationModel::Background::BackgroundWorkCost::CurrentBackgroundWorkCost() };
    auto settings{ Windows::Storage::ApplicationData::Current().LocalSettings() };
    std::wstring costAsString{ L"Low" };
    if (cost == Windows::ApplicationModel::Background::BackgroundWorkCostValue::Medium) costAsString = L"Medium";
    else if (cost == Windows::ApplicationModel::Background::BackgroundWorkCostValue::High) costAsString = L"High";
    settings.Values().Insert(L"BackgroundWorkCost", winrt::box_value(costAsString));

    // Associate a cancellation handler with the background task.
    taskInstance.Canceled({ this, &ExampleBackgroundTask::OnCanceled });

    // Get the deferral object from the task instance, and take a reference to the taskInstance.
    m_deferral = taskInstance.GetDeferral();
    m_taskInstance = taskInstance;

    Windows::Foundation::TimeSpan period{ std::chrono::seconds{1} };
    m_periodicTimer = Windows::System::Threading::ThreadPoolTimer::CreatePeriodicTimer([this](Windows::System::Threading::ThreadPoolTimer timer)
    {
        if (!m_cancelRequested && m_progress < 100)
        {
            m_progress += 10;
            m_taskInstance.Progress(m_progress);
        }
        else
        {
            m_periodicTimer.Cancel();

            // Write to LocalSettings to indicate that this background task ran.
            auto settings{ Windows::Storage::ApplicationData::Current().LocalSettings() };
            auto key{ m_taskInstance.Task().Name() };
            settings.Values().Insert(key, (m_progress < 100) ? winrt::box_value(L"Canceled") : winrt::box_value(L"Completed"));

            // Indicate that the background task has completed.
            m_deferral.Complete();
        }
    }, period);
}
void ExampleBackgroundTask::Run(IBackgroundTaskInstance^ taskInstance)
{
    // Query BackgroundWorkCost
    // Guidance: If BackgroundWorkCost is high, then perform only the minimum amount
    // of work in the background task and return immediately.
    auto cost = BackgroundWorkCost::CurrentBackgroundWorkCost;
    auto settings = ApplicationData::Current->LocalSettings;
    settings->Values->Insert("BackgroundWorkCost", cost.ToString());

    // Associate a cancellation handler with the background task.
    taskInstance->Canceled += ref new BackgroundTaskCanceledEventHandler(this, &ExampleBackgroundTask::OnCanceled);

    // Get the deferral object from the task instance, and take a reference to the taskInstance.
    TaskDeferral = taskInstance->GetDeferral();
    TaskInstance = taskInstance;

    auto timerDelegate = [this](ThreadPoolTimer^ timer)
    {
        if ((CancelRequested == false) &&
            (Progress < 100))
        {
            Progress += 10;
            TaskInstance->Progress = Progress;
        }
        else
        {
            PeriodicTimer->Cancel();

            // Write to LocalSettings to indicate that this background task ran.
            auto settings = ApplicationData::Current->LocalSettings;
            auto key = TaskInstance->Task->Name;
            settings->Values->Insert(key, (Progress < 100) ? "Canceled with reason: " + CancelReason.ToString() : "Completed");

            // Indicate that the background task has completed.
            TaskDeferral->Complete();
        }
    };

    TimeSpan period;
    period.Duration = 1000 * 10000; // 1 second
    PeriodicTimer = ThreadPoolTimer::CreatePeriodicTimer(ref new TimerElapsedHandler(timerDelegate), period);
}