等待后台任务完成
本文关键字:后台任务 等待 | 更新日期: 2023-09-27 18:35:40
我有一个BackgroundTask
生成一些文本,然后将其作为文件保存到LocalFolder
。在BackgroundTask
生成此文件后,我需要与我的主项目(相同的 VS 解决方案)一起获取该文件,并对其进行下一步工作。后台任务由用户手动触发(通过"重新加载"按钮),每 15 分钟由TimeTrigger
触发一次。
这是相关的代码片段:
syncTrigger.RequestAsync();
articles = await getCachedArticles("");
如何告诉getCachedArticles
方法等到上一个请求完成后再运行?谢谢!
理解正确,您需要做的是等待BackgroundTaskRegistration.Completed
事件触发。
一种方法是创建一个扩展方法,该方法返回在事件触发时完成的Task
:
public static Task<BackgroundTaskCompletedEventArgs> CompletedAsync(
this BackgroundTaskRegistration registration)
{
var tcs = new TaskCompletionSource<BackgroundTaskCompletedEventArgs>();
BackgroundTaskCompletedEventHandler handler = (s, e) =>
{
tcs.SetResult(e);
registration.Completed -= handler;
};
registration.Completed += handler;
return tcs.Task;
}
然后,您将像这样使用它:
var taskCompleted = registration.CompletedAsync();
await syncTrigger.RequestAsync();
await taskCompleted;
articles = await getCachedArticles("");
请注意,代码在调用RequestAsync()
之前调用CompletedAsync()
以确保在触发任务之前注册偶数处理程序,以避免在注册处理程序之前完成任务的争用情况。
每当您希望当前上下文等待异步方法完成后再继续时,都需要使用 await
关键字:
await syncTrigger.RequestAsync(); //the line below will not be executed until syncTrigger.RequestAsync() completes its task
articles = await getCachedArticles("");
我建议通读 await C# 参考文章,以便全面了解它的工作原理。
编辑:svick的答案显示了最好的方法,我什至写了一篇关于它的博客文章。以下是我最初的答案,其中包含一些可能适用于某些情况的间接替代方案。
正如其他人所指出的那样,等待syncTrigger.RequestAsync()
无济于事,尽管这仍然是一个好主意。当后台任务成功触发时,它将恢复执行,因此允许您检查它是否因任何原因失败。
可以创建一个应用服务,使其在从应用程序触发后台任务时正常工作。应用服务的行为类似于 Web 服务。它们在后台任务中运行,但具有请求-响应语义。
在后台服务中,您需要处理RequestReceived
事件:
public void Run(IBackgroundTaskInstance taskInstance)
{
var details = taskInstance.TriggerDetails as AppServiceTriggerDetails;
appServiceconnection = details.AppServiceConnection;
appServiceconnection.RequestReceived += OnRequestReceived;
}
private async void OnRequestReceived(AppServiceConnection sender,
AppServiceRequestReceivedEventArgs args)
{
var messageDeferral = args.GetDeferral();
ValueSet arguments = args.Request.Message;
ValueSet result = new ValueSet();
// read values from arguments
// do the processing
// put data for the caller in result
await args.Request.SendResponseAsync(result);
messageDeferral.Complete();
}
然后,在客户端中调用应用服务:
var inventoryService = new AppServiceConnection();
inventoryService.AppServiceName = "from manifest";
inventoryService.PackageFamilyName = "from manifest";
var status = await inventoryService.OpenAsync();
var arguments = new ValueSet();
// set the arguments
var response = await inventoryService.SendMessageAsync(arguments);
if (response.Status == AppServiceResponseStatus.Success)
{
var result = response.Message;
// read data from the result
}
有关应用服务的详细信息,请查看链接页。
也可以从计划的后台任务调用同一应用服务,但在这种情况下,无法在处理完成时收到通知。
由于您已经提到要通过 LocalFolder
中的文件交换数据,因此您的应用程序可以尝试监视对该文件的更改:
private async Task Init()
{
var storageFolder = ApplicationData.Current.LocalFolder;
var monitor = storageFolder.CreateFileQuery();
monitor.ContentsChanged += MonitorContentsChanged;
var files = await monitor.GetFilesAsync();
}
private void MonitorContentsChanged(IStorageQueryResultBase sender, object args)
{
// react to the file change - should mean the background task completed
}
据我所知,您只能监视文件夹中的所有更改,而无法真正确定事件处理程序内部的更改,因此对于您的情况,最好有一个单独的子文件夹,仅包含后台任务保存的文件一旦完成。这样,事件只会在您需要时才引发。
不过,您必须自己检查这种方法是否足够可靠。
您需要等待 RequestAsync 方法完成并显示结果https://msdn.microsoft.com/en-us/library/windows/apps/windows.applicationmodel.background.devicetriggerresult
之后,我建议使用 task.delay 等待几秒钟并尝试获取数据。
更新: 当您获得设备触发结果时,您需要先检查此结果以尝试获取数据,之后您可以假设您已保存数据。我之前建议使用 Task.Delay,只是等待几秒钟以确保保存所有数据,因为有时该过程比预期多花费几毫秒。我这样做是因为我们没有像TriggerDone这样的事件,我需要创建自己的方法。我之前在我的应用程序中这样做过,它运行良好。