等待后台任务完成

本文关键字:后台任务 等待 | 更新日期: 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这样的事件,我需要创建自己的方法。我之前在我的应用程序中这样做过,它运行良好。