處理已取消的背景工作Handle a cancelled background task

重要 APIImportant APIs

了解如何建立一個可辨識取消要求、停止工作並使用永續性儲存體向應用程式回報取消的背景工作。Learn how to make a background task that recognizes a cancellation request, stops work, and reports the cancellation to the app using persistent storage.

本主題假設您已經建立背景工作類別,包括當做背景工作進入點使用的 Run 方法。This topic assumes you have already created a background task class, including the Run method that is used as the background task entry point. 若要快速開始建立背景工作,請參閱建立及註冊跨處理序背景工作建立及註冊同處理序背景工作To get started quickly building a background task, see Create and register an out-of-process background task or Create and register an in-process background task. 如需條件與觸發程序的深入資訊,請參閱使用背景工作支援應用程式For more in-depth information on conditions and triggers, see Support your app with background tasks.

本主題也適用於同處理序背景工作。This topic is also applicable to in-process background tasks. 但是,請改用OnBackgroundActivated取代Run方法。But instead of the Run method, substitute OnBackgroundActivated. 同處理序背景工作並不需要您使用永續性儲存體來發出取消訊號,因為背景工作是在與您前景應用程式相同的處理序中執行,所以您可以使用應用程式狀態來傳達取消。In-process background tasks do not require you to use persistent storage to signal the cancellation because you can communicate the cancellation using app state since the background task is running in the same process as your foreground app.

使用 OnCanceled 方法辨識取消要求Use the OnCanceled method to recognize cancellation requests

撰寫方法以處理取消事件。Write a method to handle the cancellation event.

注意

針對桌上型電腦以外的所有裝置系列,如果裝置的記憶體變成不足,背景工作可能就會終止。For all device families except desktop, if the device becomes low on memory, background tasks may be terminated. 如果未顯示記憶體不足的例外狀況,或應用程式未處理此例外狀況,則背景工作會在沒有警告的情況下終止,而不會引發 OnCanceled 事件。If an out of memory exception is not surfaced, or the app doesn't handle it, then the background task will be terminated without warning and without raising the OnCanceled event. 這有助於確保前景應用程式的使用者體驗。This helps to ensure the user experience of the app in the foreground. 您的背景工作應該要設計成能夠處理這種情況。Your background task should be designed to handle this scenario.

依下列方式建立名為 OnCanceled 的方法。Create a method named OnCanceled as follows. 這個方法是當針對您的背景工作提出取消要求時,由「Windows 執行階段」呼叫的進入點。This method is the entry point called by the Windows Runtime when a cancellation request is made against your background task.

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.
}

將名為** _ CancelRequested**的旗標變數新增至背景工作類別。Add a flag variable called _CancelRequested to the background task class. 此變數將用來指示何時提出取消要求。This variable will be used to indicate when a cancellation request has been made.

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

在您于步驟1中建立的OnCanceled方法中,將旗標變數** _ CancelRequested設定為true**。In the OnCanceled method you created in step 1, set the flag variable _CancelRequested to true.

完整的背景工作範例 OnCanceled方法會將** _ CancelRequested設定為true** ,並寫入可能有用的 debug 輸出。The full background task sample OnCanceled method sets _CancelRequested to true and writes potentially useful debug output.

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;
}

在背景工作的 Run 方法中,于開始工作之前註冊 OnCanceled 事件處理常式方法。In the background task's Run method, register the OnCanceled event handler method before starting work. 在同處理序背景工作中,您可以在應用程式初始化的過程中進行這項註冊。In an in-process background task, you might do this registration as part of your application initialization. 例如,使用下列這行程式碼。For example, use the following line of code.

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

藉由結束背景工作來處理取消Handle cancellation by exiting your background task

收到取消要求時,執行背景工作的方法需要在** _ cancelRequested設定為true**時,停止工作並結束。When a cancellation request is received, your method that does background work needs to stop work and exit by recognizing when _cancelRequested is set to true. 針對同進程背景工作,這表示從 OnBackgroundActivated 方法傳回。For in-process background tasks, this means returning from the OnBackgroundActivated method. 若為跨進程背景工作,這表示從 Run 方法傳回。For out-of-process background tasks, this means returning from the Run method.

修改背景工作類別的程式碼,以便在旗標變數運作時檢查旗標變數。Modify the code of your background task class to check the flag variable while it's working. 如果** _ cancelRequested**設為 true,就會停止繼續工作。If _cancelRequested becomes set to true, stop work from continuing.

背景工作範例包含檢查,如果背景工作已取消,就會停止定期計時器回呼。The background task sample includes a check that stops the periodic timer callback if the background task is canceled.

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.
}

注意

上方顯示的程式碼範例會使用 IBackgroundTaskInstance。用來記錄背景工作進度的進度 屬性。The code sample shown above uses the IBackgroundTaskInstance.Progress property being used to record background task progress. 進度會透過 BackgroundTaskProgressEventArgs 類別回報給應用程式。Progress is reported back to the app using the BackgroundTaskProgressEventArgs class.

修改 Run 方法,如此一來,當工作停止之後,就會記錄工作是否已完成或已取消。Modify the Run method so that after work has stopped, it records whether the task completed or was cancelled. 此步驟適用於跨處理序背景工作,因為您需要一個當背景工作被取消時,可在處理序之間通訊的方法。This step applies to out-of-process background tasks because you need a way to communicate between processes when the background task was cancelled. 針對同處理序背景工作,您只能與應用程式分享狀態以指出工作已被取消。For in-process background tasks, you can simply share state with the application to indicate the task was cancelled.

背景工作範例會記錄 LocalSettings 中的狀態。The background task sample records status in LocalSettings.

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

備註Remarks

您可以下載背景工作範例,查看方法內容中的這些程式碼範例。You can download the background task sample to see these code examples in the context of methods.

為了方便說明,範例程式碼只會顯示「 執行 」方法的部分 (和「背景工作」 範例) 的回呼計時器。For illustrative purposes, the sample code shows only portions of the Run method (and callback timer) from the background task sample.

Run 方法範例Run method example

背景工作範例中的完整Run方法和計時器回呼程式碼會顯示在下列內容中。The complete Run method, and timer callback code, from the background task sample are shown below for context.

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