处理取消的后台任务
重要的 API
了解如何创建一个后台任务,该任务识别取消请求、停止工作,并向使用永久性存储的应用报告取消。
本主题假定你已创建一个后台任务类,其中包含用作后台任务入口点的 Run 方法。 若要快速生成后台任务,请参阅创建和注册进程外后台任务或创建和注册进程内后台任务。 有关条件和触发器的更多深入信息,请参阅使用后台任务支持应用。
本主题也适用于进程内后台任务。 但是,使用 OnBackgroundActivated 替换 Run 方法。 进程内后台任务不需要你使用永久性存储发送取消信号,因为你可以使用应用状态传达取消(如果后台任务与前台应用在同一进程中运行)。
使用 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);
}