13

我正在尝试使用 .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. GetContainerReference并且GetBlockBlobReference不需要异步,因为他们还没有联系服务器,对吧?

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

  3. 我对什么有点困惑BeginDownloadToStream..到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();
4

3 回答 3

17

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

是的。

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

是的。特别是,TaskFactory.FromAsync如果您传入Begin/End方法对而不是传入现有的IAsyncResult. 像这样:

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

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

await blog.DownloadToStreamAsync(memoryStream);

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

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

正确的。

streamReader.ReadToEnd 是否需要异步?

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

所以你不想调用ReadToEndAsync.MemoryStream

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

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

于 2013-08-14T02:00:57.433 回答
1
  1. 正确,如果它们不会是长时间的操作,它们就不需要异步,它们不应该是。

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

  3. 见#2。

于 2013-08-13T19:54:09.417 回答
0

请参阅: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)

于 2013-09-16T23:03:34.620 回答