Async/Await 和 CreatePeriodicTimer 失败

Async/Await and CreatePeriodicTimer failing

我有一个执行以下操作的应用程序:

  1. 初始化连接的网络摄像头

    private async Task<MediaCapture> InitialiseWebCam()
    {
        MediaCapture webCam = null;
        webCam = new MediaCapture();
    
        try
        {
            await webCam.InitializeAsync();
        }
        catch (Exception ex)
        {
            //Log error
        }
    
        return webCam;
    }
    
  2. 拍照

    private async Task<StorageFile> TakePicture(MediaCapture webCam)
    {
        try
        {
            var image = await KnownFolders.PicturesLibrary.CreateFileAsync("TestImage.jpg", CreationCollisionOption.GenerateUniqueName);
    
            var imageEncodingProperties = ImageEncodingProperties.CreatePng();
            await webCam.CapturePhotoToStorageFileAsync(imageEncodingProperties, image);
    
            return image;
        }
        catch (Exception ex)
        {
            //Log error
        }
        return null;
    }
    
  3. 将图像上传到 Azure Blob 存储

    private async Task<StorageFile> UploadPictureToAzure(StorageFile image)
    {
    string Azure_StorageAccountName = "MYACCOUNTNAME";
    string Azure_ContainerName = "MYCONTAINERNAME";
    string Azure_AccessKey = "MYACCESSKEY";
    
        try
        {
            StorageCredentials creds = new StorageCredentials(Azure_StorageAccountName, Azure_AccessKey);
            CloudStorageAccount account = new CloudStorageAccount(creds, useHttps: true);
            CloudBlobClient client = account.CreateCloudBlobClient();
            CloudBlobContainer sampleContainer = client.GetContainerReference(Azure_ContainerName);
    
            CloudBlockBlob blob = sampleContainer.GetBlockBlobReference(image.Name);
    
            await blob.UploadFromFileAsync(image);
    
            return image;
        }
        catch (Exception ex)
        {
            //Log
        }
    
        return image;
    }
    

三个方法调用如下:

public void Run(IBackgroundTaskInstance taskInstance)
{
    Monitor();
}

private void Monitor()
{
    var webCam = InitialiseWebCam().Result;
    var image = TakePicture(webCam).Result;

    var output = UploadPictureToAzure(image).Result;
}

此时一切正常,图像出现在我的 blob 存储中。我希望此行为 运行 作为 Windows IoT 上的无头后台应用程序,因此我进行了以下调整:

public void Run(IBackgroundTaskInstance taskInstance)
{
    taskInstance.GetDeferral();

    timer = ThreadPoolTimer.CreatePeriodicTimer(Timer_Tick, TimeSpan.FromSeconds(60));
}

private void Timer_Tick(ThreadPoolTimer threadPoolTimer)
{
    Monitor();
}

有了这些更改,Monitor 功能将按预期触发,但是 UploadPictureToAzure(image).Result 的输出为空,即使 webCamimage 正在按预期工作,应用程序突然退出。

出现上述行为的原因是什么?我期待图像被上传并且整个过程继续在创建的计时器循环内发生。

编辑:

我试图通过像这样调整 UploadPictureToAzure 来强制同步行为:

private void UploadPictureToAzure(StorageFile image)
{
    string Azure_StorageAccountName = "ACCOUNTNAME";
    string Azure_ContainerName = "CONTAINERNAME";
    string Azure_AccessKey = "ACCESSKEY";

    try
    {
         StorageCredentials creds = new StorageCredentials(Azure_StorageAccountName, Azure_AccessKey);
         CloudStorageAccount account = new CloudStorageAccount(creds, useHttps: true);
         CloudBlobClient client = account.CreateCloudBlobClient();
         CloudBlobContainer sampleContainer = client.GetContainerReference(Azure_ContainerName);

         CloudBlockBlob blob = sampleContainer.GetBlockBlobReference(image.Name);

         var response = blob.UploadFromFileAsync(image);
         response.AsTask().Wait();
     }
     catch (Exception ex)
     {
         //Log
     }
}

现在即使调用 .Wait(),也会立即执行 returns 并且应用程序退出。

如果我删除 ThreadPoolTimer.CreatePeriodicTimer 的用法,并将其替换为 while 循环,则图像上传不会出现问题。

异步方法的问题是它们倾向于爬上你的代码并侵入它周围:)...你必须使用 await 关键字而不是调用 Result 属性,否则在尝试获取结果之前,您将无法确定该函数是否确实完成了执行。这适用于您代码中的所有异步方法。

将您的 Monitor 方法更改为异步和 return Task,如下所示:

private async Task Monitor()
{
    var webCam = await InitialiseWebCam();
    var image = await TakePicture(webCam);
    var output = await UploadPictureToAzure(image);
}

我猜 Windows phone 在顶层有一个异步 API,所以你可以等待到主线程,但如果没有,here are a couple of workarounds to call async methods synchronously

编辑:

根据 Quickstart: Create and register a background task (XAML),您可以在 运行 方法上执行以下操作:

public async void Run(IBackgroundTaskInstance taskInstance)
{
    BackgroundTaskDeferral _deferral = taskInstance.GetDeferral();

    ThreadPoolTimer.CreatePeriodicTimer(async(timer) => { 
        await Monitor();
        _deferral.Complete();
    }, TimeSpan.FromSeconds(60));


}

最后我使用下面的代码实现了我所需要的:

private readonly TimeSpan _delayDuration = TimeSpan.FromMinutes(20);

public void Run(IBackgroundTaskInstance taskInstance)
{
    taskInstance.GetDeferral();

    do
    {
        try
        {
            Monitor().Wait();
        }
        /* SNIP */
    }
    while (true);

private async Task Monitor()
{
    var webCam = await InitialiseWebCam();
    var image = await TakePicture(webCam);
    await UploadPictureToAzure(image);
    await Task.Delay(_delayDuration);
}


private async Task UploadPictureToAzure(StorageFile image)
    {
        var Azure_StorageAccountName = "accountname";
        var Azure_ContainerName = "container";
        var Azure_AccessKey = "key";
        try
        {
            StorageCredentials creds = new StorageCredentials(Azure_StorageAccountName, Azure_AccessKey);
            CloudStorageAccount account = new CloudStorageAccount(creds, true);
            CloudBlobClient client = account.CreateCloudBlobClient();
            CloudBlobContainer sampleContainer = client.GetContainerReference(Azure_ContainerName);
        CloudBlockBlob blob = sampleContainer.GetBlockBlobReference(image.Name);

        await blob.UploadFromFileAsync(image);
    }
    catch (Exception ex)
    {
        /* Logging */
        throw ex;
    }
}

private async Task<MediaCapture> InitialiseWebCam()
{
    var webCam = new MediaCapture();

    try
    {
        await webCam.InitializeAsync();
    }
    catch (Exception ex)
    {
       /* Logging */
       throw ex;
    }

    return webCam;
}

private async Task<StorageFile> TakePicture(MediaCapture webCam)
{
    try
    {
        var image = await KnownFolders.PicturesLibrary.CreateFileAsync("Image.png", CreationCollisionOption.ReplaceExisting);
        var imageEncodingProperties = ImageEncodingProperties.CreatePng();

        await webCam.CapturePhotoToStorageFileAsync(imageEncodingProperties, image);

        return image;
    }
    catch (Exception ex)
    {
        /* Logging */
        throw ex;
    }
}