【问题标题】:How to stream unknown amount of bytes to server using BackgroundUploader?如何使用 BackgroundUploader 将未知数量的字节流式传输到服务器?
【发布时间】:2014-03-11 21:54:16
【问题描述】:

我有一个繁重的 IO/CPU 转换“过程”,从文件类型 A 转换为 B。目前我将其写入 StorageFile,然后在转换完成后使用 BackgroundUploader 上传。

但是,我想尽快开始流式传输,同时仍在生成输出文件。此外,我什至不一定要创建输出 StorageFile,而是“随时随地”上传。

注意,我不知道转换过程中输出文件的最终大小,它可能比源文件小或大。

首先,我尝试在写入时简单地打开接收器输出 StorageFile,并将该流传递给 BackgroundUploader,但这会导致“竞争条件”,即上传在到达写入的字节末尾时终止StorageFile(当它赶上转换工作时)。

除了流式传输到 StorageFile,我还可以将输出字节写入缓冲区,例如 2KiB。我想在转换写入后上传这个缓冲区。

//simplified code...
uint bytesRead = 0;
byte[] buffer = new byte[buff_sz];
bytesRead = converter.Read(buffer);
while(bytesRead > 0)
{
    // here I would like to 'upload' the data (maybe using BackgroundUploader? or some other API?)
    bytesRead = converter.Read(buffer);
}

如果不遇到与以前相同的“竞争条件”,我不确定如何做到这一点。在将新字节放入缓冲区之前,如何保持 BackgroundUploader 运行?

注意 1:最后一次迭代将是 bytesRead

注意 2:转换代码位于跨平台 C++ 共享库中。

谢谢!

补充

根据 Nate Diamond 的建议,我研究了 IINputStream 接口。下面的代码可以工作,虽然可能是您编写的性​​能最差的版本,但足以证明这个概念。

以下基于“maxim pg”实现的包装代码从这里开始。 How can I implement IRandomAccessStream in C#?

 public IAsyncOperationWithProgress<IBuffer, UInt32> ReadAsync(
    IBuffer buffer,// The buffer into which the asynchronous read operation places the bytes that are read. 
    uint count,// The number of bytes to read that is less than or equal to the Capacity value.
    InputStreamOptions options) // Specifies the type of the asynchronous read operation.
{
    if (buffer == null) throw new ArgumentNullException("buffer");
    Func<CancellationToken, IProgress<uint>, Task<IBuffer>> taskProvider =
    (token, progress) => ReadBytesAsync(buffer, count, token, progress, options);
    return AsyncInfo.Run(taskProvider);
}

private Task<IBuffer> ReadBytesAsync(
    IBuffer buffer, 
    uint count, 
    CancellationToken token, 
    IProgress<uint> progress, 
    InputStreamOptions options)
{
    TaskCompletionSource<IBuffer> cts = new TaskCompletionSource<IBuffer>();
    try
    {
        var ignore = ThreadPool.RunAsync((handler) => {
            _buffer = new byte[count];
            uint bytesRead = _reader.Read(_buffer); // this triggers file conversion work
            buffer.Length = bytesRead; // this is important apparently, otherwise no data written!
            Stream stream = buffer.AsStream();
            stream.Write(_buffer, 0, (int)bytesRead);
            stream.Flush();
            cts.TrySetResult(buffer);
        });
    }
    catch(Exception e)
    {
        cts.SetException(e);
    }
    return cts.Task;
}

【问题讨论】:

  • 所以,我注意到的一件事是 IInputStream(BackgroundUploader.CreateUploadFromStreamAsync 接受的内容)是一个简单的接口。它有一个你必须重写的方法。我在想的是,您可以创建自己的 CustomInputStream 类,该类接受输入并不断允许读取直到关闭。 Others seam to have had success with similar concepts.
  • 不过,我不确定这是否真的必要。您介意发布您以前尝试过但导致竞争条件的代码吗?就像,您可以制作一个简单的InMemoryRandomAccessStream 而不将其写入文件并使用它吗? (为此目的,它有一个GetInputStreamAt 方法)。
  • 感谢@NateDiamond,我听取了您关于实现 IInputStream 接口的建议。这并不完全直截了当,尤其是对于像我这样的 TAP 菜鸟。我会将我正在使用的代码添加到我的问题中,以防有人关心。请提交答案,我会投票。

标签: c# windows-store-apps


【解决方案1】:

我相信最简单的方法是创建一个自定义IInputStream。这是一个只有一种方法的接口,ReadAsync

然后您所要做的就是创建您的CustomInputStream,当它有足够的内容来填充请求的块时,它会不断地允许输入并从ReadAsync 返回。它还有一个Close 函数告诉它已经完成了。

Others have implemented something similar in IRandomAccessStream successfully.

【讨论】:

  • 感谢您的建议。它现在“工作”,但奇怪的是 BackgroundUploader::CreateUploadFromStreamAsync 在转换完成之前不会返回。我认为这是因为上传需要事先知道 HTTP 请求标头的内容大小。不使用 StorageFile 作为中介,我可能仍然有性能优势,但显然这会炸毁内存,因为我可能会上传数百兆!
  • BackgroundUploader 如何处理上传流很重要。您的自定义流没有理由无法从内存中删除已发送给上传者的内容。您是否尝试在 Read 方法中设置断点并查看它是重复调用还是仅调用一次?您拥有的另一个选择是将其分成块。然后,您可以在每个块的前面放置一个标头作为索引,并让您的服务器在收到它们后将它们重新组合在一起。
  • 是的,这个问题看起来肯定是 BackgroundUploader 想要 PUT 方法的 Content-Length 标头。读取一直被调用,直到我说写入零字节,然后它再也不会调用。不确定我是否可以使用 BackgroundUploader 进行块上传,可能会切换到 System.Net.Http.HttpClient(我将其用于其余命令,并且不使用 Windows Store HttpClient 之类的 UI)。但是,我正在尝试上传到 AWS S3 ......这不允许块! Rackspace 会,或者我会先上传到我们自己的服务器,我们的服务器可以上传到 S3,因为它现在知道大小。
  • According to this it should,虽然它是为大文件设计的。
  • 感谢您的快速回复。我指的是 Transfer-Encoding: 分块。 aws.amazon.com/articles/1109#14 虽然我可以处理数百兆的文件,但通常它们“希望”只有几兆,甚至小于 1MB。您发送的链接有 5MB 的限制,但肯定是我们在某些情况下使用的东西。再次感谢您的帮助。
猜你喜欢
  • 2022-01-20
  • 1970-01-01
  • 2013-10-03
  • 1970-01-01
  • 1970-01-01
  • 2011-05-20
  • 1970-01-01
  • 2012-08-12
  • 2012-03-06
相关资源
最近更新 更多