處理已取消的背景工作
重要 API
了解如何建立一個背景工作來識別取消請求、停止工作並使用持久性儲存向應用程式報告取消情況。
本主題假設您已經建立了一個背景工作類,包括用作背景工作入口點的 Run 方法。 若要開始快速建置背景工作,請參閱建立並註冊程序外背景工作或建立並註冊程序內背景工作。 有關條件和觸發程序的更深入資訊,請參閱透過背景工作支援您的應用程式。
本主題也適用於程序背景工作。 但取代 Run 方法,請取代 OnBackgroundActivated。 程序內背景工作不需要您使用持久性儲存來發出取消訊號,因為您可以使用應用程式狀態來傳達取消訊息,因為背景工作與前景應用程式在同一程序中執行。
使用 OnCanceled 方法來辨識取消要求
撰寫方法來處理取消事件。
注意
針對桌上型電腦以外的所有裝置系列,如果裝置記憶體不足,背景工作可能會終止。 如果未顯示記憶體不足例外狀況,或應用程式未處理例外狀況,則背景工作將會在沒有警告的情況下終止,並且不會觸發 OnCanceled 事件。 這有助於確保應用程式在前景的使用者體驗。 您的背景工作應該設計為處理此案例。
建立名為 OnCanceled 的方法,如下所示。 此方法是針對背景工作提出取消要求時,Windows 執行階段所呼叫的進入點。
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 的旗標變數新增至背景工作類別。 此變數將用來指出何時提出取消要求。
volatile bool _CancelRequested = false;
private:
volatile bool m_cancelRequested;
private:
volatile bool CancelRequested;
在步驟 1 中建立的 OnCanceled 方法中,將標誌變數 _CancelRequested 設為 true。
完整背景工作範例 OnCanceled 方法會將_CancelRequested設定為 true,並寫入可能有用的偵錯輸出。
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 事件處理程式方法,再開始工作。 在同程序背景工作中,您可能會在應用程式初始化過程中執行這項註冊。 例如,使用以下程式碼行。
taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);
taskInstance.Canceled({ this, &ExampleBackgroundTask::OnCanceled });
taskInstance->Canceled += ref new BackgroundTaskCanceledEventHandler(this, &ExampleBackgroundTask::OnCanceled);
結束背景工作來處理取消
收到取消要求時,執行背景工作的方法必須藉由辨識 _cancelRequested 設為 true 來停止工作和結束。 對於處理中背景工作,這表示從 OnBackgroundActivated 方法傳回。 對於跨程序背景工作,這表示會從 Run 方法傳回。
修改背景工作類別的程序代碼,以在工作時檢查旗標變數。 如果 _cancelRequested 設為 true,請停止工作繼續。
背景工作範例包含一項檢查,如果取消背景工作,則停止定期計時器回呼。
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.Progress 屬性來記錄背景工作進度。 進度會使用 BackgroundTaskProgressEventArgs 類別回報回應用程式。
修改 Run 方法,以便在工作停止後,記錄工作是完成還是取消。 此步驟適用於跨程序背景工作,因為您需要在取消背景工作時,在程序之間進行通訊的方法。 針對同程序背景工作,您可以只與應用程式共享狀態,以指出工作已取消。
背景工作範例在 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();
}
備註
您可以下載背景工作範例以在方法上下文中查看這些程式碼範例。
出於說明目的,範例程式碼僅顯示背景工作範例中的 Run 方法 (和回呼計時器) 的一部分。
Run 方法範例
背景工作範例中的完整 Run 方法和定時器回呼程式代碼如下所示,以供內容使用。
// 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);
}