【问题标题】:Asynchronous download of an Azure blob to string with .NET 4.5 async, await使用 .NET 4.5 异步将 Azure blob 下载到字符串,等待
【发布时间】:2013-08-15 13:45:00
【问题描述】:

我正在尝试使用 .NET 4.5 async & await 实现 完全异步 blob 下载。

假设整个 blob 可以同时放入内存中,并且我们希望将其保存在 string 中。

代码:

public async Task<string> DownloadTextAsync(ICloudBlob blob)
{
    using (Stream memoryStream = new MemoryStream())
    {
        IAsyncResult asyncResult = blob.BeginDownloadToStream(memoryStream, null, null);
        await Task.Factory.FromAsync(asyncResult, (r) => { blob.EndDownloadToStream(r); });
        memoryStream.Position = 0;

        using (StreamReader streamReader = new StreamReader(memoryStream))
        {
            // is this good enough?
            return streamReader.ReadToEnd();

            // or do we need this?
            return await streamReader.ReadToEndAsync();
        }
    }
}

用法:

CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageAccountConnectionString"));
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("container1");
CloudBlockBlob blockBlob = container.GetBlockBlobReference("blob1.txt");

string text = await DownloadTextAsync(blockBlob);

这段代码是否正确并且确实是完全异步的?你会以不同的方式实现吗?

我希望得到一些额外的说明:

  1. GetContainerReferenceGetBlockBlobReference 不需要异步,因为它们还没有联系服务器,对吧?

  2. streamReader.ReadToEnd 是否需要异步?

  3. 我对@9​​87654327@ 做了什么有点困惑。在调用EndDownloadToStream 时,我的内存流中是否包含所有 数据?还是流只打开预读?

更新:(自 Storage 2.1.0.0 RC 起)

现在原生支持异步。

CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageAccountConnectionString"));
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("container1");
CloudBlockBlob blockBlob = container.GetBlockBlobReference("blob1.txt");

string text = await blockBlob.DownloadTextAsync();

【问题讨论】:

    标签: c# azure .net-4.5 async-await azure-blob-storage


    【解决方案1】:
    1. 正确,如果它们不应该是长操作,它们就不需要异步。

    2. 可能不是,虽然我不熟悉这个特定的实现。我希望,由于您正在等待流在此之前结束,因此此时应该没有网络工作,因此也没有昂贵的操作。您应该只是从缓冲区中提取数据,而且速度应该很快。然而,这很容易测试。您可以使用 Fiddler 之类的工具来查看该调用期间是否正在进行网络通信,您可以对该方法进行计时以查看它是否需要足够长的时间才能显示网络 IO 正在进行,或者您可以查看此文档具体流实现。或者你可以只使用 async 方法来保证安全,我建议这样做,而不是冒被弄错的风险。我会很惊讶地发现这个需要是异步的。

    3. 参见 #2。

    【讨论】:

    • 文档在这件事上并不太冗长。我想测试是有序的,我会采取一个大的 blob 并做一些时间。这太棘手了,我希望它很明显:)
    • BeginDownloadToStream 上的文档很清楚 - 当“操作完成”时调用回调(在您的情况下,调用 EndDownloadToStream 的代码)。
    • @PeterRitchie 我不能 100% 确定操作到底是什么。是整个下载还是只是开始(服务器的第一个查询)。如果是前者,我是否有任何其他方式来获得进度通知或如果它太长/太大则取消?
    • @talkol 这是整个下载。如果它不能发生(太大等),你会得到一个例外。我不认为 CloudBlob 实现了进步,但您可以使用 Stream 实现,例如 mel-green.com/2010/01/progressstream
    • @PeterRitchie 太好了。所以我会 CloudBlob.OpenRead() 来获取流,然后用 StreamReader.ReadAsync() 自己实现进度来读取它,对吧?
    【解决方案2】:

    这段代码是否正确,并且确实是完全异步的?

    是的。

    你会以不同的方式实现吗?

    是的。特别是,如果您传入Begin/End 方法对而不是传入现有的IAsyncResult,则TaskFactory.FromAsync 包装器效率更高。像这样:

    await Task.Factory.FromAsync(blob.BeginDownloadToStream,
        blob.EndDownloadToStream, memoryStream, null);
    

    我也更喜欢将它们包装成单独的扩展方法,这样我就可以这样称呼它:

    await blog.DownloadToStreamAsync(memoryStream);
    

    请注意,客户端库的下一版本(2.1,目前在 RC 中)将具有 async-ready 方法,即 DownloadToStreamAsync

    GetContainerReference 和 GetBlockBlobReference 不需要异步,因为它们还没有联系服务器,对吧?

    正确。

    streamReader.ReadToEnd 是否需要异步?

    它没有(也不应该)。 Streamasync 编程的一个不寻常的案例。通常,如果有 async 方法,那么您应该在您的 async 代码中使用它,但该准则不适用于 Stream 类型。原因是Stream 基类不知道它的实现是同步的还是异步的,因此它假定它是同步的,并且默认情况下会通过仅在后台线程上执行同步工作来伪造其异步操作。真正的异步流(例如,NetworkStream)会覆盖它并提供真正的异步操作。同步流(例如,MemoryStream)保持此默认行为。

    所以你想在MemoryStream上打电话给ReadToEndAsync

    我对 BeginDownloadToStream 的作用有点困惑。在调用 EndDownloadToStream 时,我的内存流中是否包含所有数据?

    是的。操作是DownloadToStream;它,它将一个blob下载到一个流中。由于您正在将 blob 下载到 MemoryStream,因此到此操作完成时,该 blob 已完全在内存中。

    【讨论】:

      【解决方案3】:

      请参阅:http://channel9.msdn.com/Events/TechEd/NorthAmerica/2013/WAD-B406#fbid=lCN9J5QiTDF 了解一些有用的最佳实践,包括为什么应避免像原始代码那样使用内存流:)

      请注意,下载 Blob 有两个主要选项,Cloud[Block|Page]Blob.Download[Range]To* 方法和 OpenRead() 提供的流。在下载 API 的情况下,整个 blob(或范围,如果请求)作为单个 GET 调用发出,结果被流式传输/写入适当的位置,在瞬态故障的情况下,尚未收到的字节子范围是根据重试策略请求。

      OpenRead 方法适用于希望在较长时间内处理数据并且不保持连接打开的客户端。它们通过指定将在客户端预缓冲的给定长度来工作,当流用完预缓冲数据时,请求下一个子范围。

      最后,从2.1 RTM 开始,提供了一个 DownloadTextAsync 方法,可以为您完成所有这些工作:)(可选重载来指定编码,默认为 UTF8)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-12-09
        • 2021-05-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-20
        相关资源
        最近更新 更多