【问题标题】:How do I properly implement async/await for image data migration?如何正确实现图像数据迁移的 async/await?
【发布时间】:2020-02-24 01:23:45
【问题描述】:

我正在编写一个控制台应用程序来在旧系统和新版本之间进行一些数据迁移。每条记录都有存储在一个 Web 服务器上的关联图像,我正在将每个图像下载/更改/上传到 Azure(并且还在数据库中记录有关每个图像的一些数据)。

这是一个粗略的大纲,在代码中:

public void MigrateData()
{
    var records = GetRecords();

    foreach (var record in records)
    {
        // ...

        MigrateImages(record.Id, record.ImageCount);
    }
}

public void MigrateImages(int recordId, int imageCount)
{
    for (int i = 1; i <= imageCount; i++)
    {
        var legacyImageData = DownloadImage("the image url");

        if (legacyImageData != null && legacyImageData.Length > 0)
        {
            // discard because we don't need the image id here, but it's used in other workflows
            var _ = InsertImage(recordId, legacyImageData);
        }
    }
}

// This method can be used elsewhere, so the return of int is necessary and cannot be changed
public int InsertImage(int recordId, byte[] imageData)
{
    var urls = UploadImage(imageData).Result;

    return // method call to save image and return image ID
}

public async Task<(Uri LargeUri, Uri ThumbnailUri)> UploadImage(byte[] imageData)
{
    byte[] largeData = ResizeImageToLarge(imageData);
    byte[] thumbnailData = ResizeImageToThumbnail(imageData);

    var largeUpload = largeBlob.UploadFromByteArrayAsync(largeImage, 0, largeImage.Length);
    var thumbUpload = thumbsBlob.UploadFromByteArrayAsync(thumbImage, 0, thumbImage.Length);

    await Task.WhenAll(largeUpload, thumbUpload);

    var largeUrl = "";// logic to build url
    var thumbUrl = "";// logic to build url

    return (largeUrl, thumbUrl);
}

我正在为 UploadImage() 使用 async/await 以允许并行上传大图和缩略图,从而节省时间。

我的问题是(如果可能/有意义)我如何利用 async/await for MigrateImages() 进行并行图像上传,以减少完成任务所需的总时间?我已经在 UploadImage() 中使用 async/await 的事实是否阻碍了我的目标?

由于我的问题,这可能很明显,但是等待/异步仍然是我无法完全理解如何正确使用和/或实现它的东西。

【问题讨论】:

  • InsertImage 应该是异步的并等待UploadImage 而不是调用Result。当InsertImage 是异步时,您可以在MigrateImages 中使用与UploadImage 中相同的模式来运行多个“插入” => 启动多个异步操作并使用Task.WhenAll 等待它们
  • “上传”这个词意味着您受 I/O 限制,而不是 CPU 限制。您需要确定并行化是否会使您受益。如果瓶颈是网络速度,那么将更多数据投入瓶颈不会提高任何速度。
  • @DanielMann 我正在尝试并行化上传图像的过程。 “同时”上传 10 张图片应该比串行上传更快,是吗?
  • 没有。如果您可以每秒上传 1 MB,并且您已经在上传每秒 1 MB,那么一次上传两个文件意味着您以每秒 1 MB 的速度上传,每个文件每秒上传 500 KB。或者其他一些加起来高达 1 MB/s 的比率。
  • @DanielMann 明白,感谢您的澄清。对于这个例子,我们可以假设上传带宽不是限制。

标签: c# async-await


【解决方案1】:

async/await 技术旨在促进异步,而不是并发。在您的情况下,您希望通过同时上传多个图像来加速图像上传过程。如果您通过阻塞它们来浪费一些线程池线程,并且您没有需要保持畅通无阻的 UI 线程,这并不重要。您正在制作一个您将使用一次或两次的工具,仅此而已。所以我建议你不要去尝试理解为什么你的应用程序没有按照你的预期运行,并且完全避免async/await。坚持使用简单而熟悉的同步编程模型来完成这项作业。

结合同步和异步代码是危险的,如果您对async/await 的经验和理解有限,则更是如此。这项技术有许多错综复杂的地方。特别是使用 Task.Result 属性是一个危险信号。当您对async/await 代码更加熟练时,您将把Result 的任何使用视为一枚未上锁的手榴弹,随时准备在您的脸上爆炸,让您看起来像个傻瓜。在具有同步上下文(Windows 窗体、ASP.NET)的应用程序中使用时,它很容易引入死锁,以至于it's not even funny

您可以通过以下方式实现所需的并发性,而无需处理异步的复杂性:

public (Uri LargeUri, Uri ThumbnailUri) UploadImage(byte[] imageData)
{
    byte[] largeData = ResizeImageToLarge(imageData);
    byte[] thumbnailData = ResizeImageToThumbnail(imageData);

    var largeUpload = largeBlob.UploadFromByteArrayAsync(
        largeImage, 0, largeImage.Length);
    var thumbUpload = thumbsBlob.UploadFromByteArrayAsync(
        thumbImage, 0, thumbImage.Length);

    Task.WaitAll(largeUpload, thumbUpload);

    var largeUrl = "";// logic to build url
    var thumbUrl = "";// logic to build url

    return (largeUrl, thumbUrl);
}

我刚刚将await Task.WhenAny 替换为Task.WaitAll,并从方法的返回值中删除了包装Task

【讨论】:

  • 谢谢,但您实际上并没有回答这个问题 - 我的代码没有意外运行,我正在寻找改进它的方法。即使您回避 async/await 的建议很好,但您的代码并没有做任何比我已经拥有的更多的事情。没有性能提升。
  • @Matt 您的代码已经执行并发上传。在混合中添加异步不会使其更快。如果您正在询问有关如何使其更快的建议,我建议首先测量顺序上传两个文件的持续时间,然后同时上传两个文件。然后测量顺序下载和上传一个文件,然后同时下载和上传一个文件的持续时间。给我们这些结果,我们也许可以提供帮助!
猜你喜欢
  • 1970-01-01
  • 2020-08-23
  • 1970-01-01
  • 2020-09-25
  • 2016-02-18
  • 1970-01-01
  • 2018-08-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多