18

ファイルのアップロードのプロトタイプを作成するための単純な WCF サービスを作成しました。サービス:

[ServiceContract]
public class Service1
{
    [OperationContract]
    [WebInvoke(Method = "POST", UriTemplate = "/Upload")]
    public void Upload(Stream stream)
    {
        using (FileStream targetStream = new FileStream(@"C:\Test\output.txt", FileMode.Create, FileAccess.Write))
        {
            stream.CopyTo(targetStream);
        }
    }
}

「Streamed」に設定して使用webHttpBindingし、すべて2GBに設定します。10MBに設定しました。transferModemaxReceivedMessageSizemaxBufferPoolSizemaxBufferSizehttpRuntimemaxRequestLength

クライアントは、次の方法で HTTP 要求を発行します。

HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(@"http://.../Service1.svc/Upload");

request.Method = "POST";
request.SendChunked = true;
request.AllowWriteStreamBuffering = false;
request.ContentType = MediaTypeNames.Application.Octet;

using (FileStream inputStream = new FileStream(@"C:\input.txt", FileMode.Open, FileAccess.Read))
{
    using (Stream outputStream = request.GetRequestStream())
    {
        inputStream.CopyTo(outputStream);
    }
}

さて、最後に、何が問題なのですか:

100MB のファイルをアップロードすると、サーバーは HTTP 400 (Bad request) を返します。WCF トレースを有効にしようとしましたが、エラーは表示されません。増やすとhttpRuntimemaxRequestLength1GB まで、ファイルは問題なくアップロードされます。MSDN によるとmaxRequestLength入力ストリームのバッファリングしきい値の制限を KB 単位で指定します」。

これにより、ファイル全体(100MBすべて)が最初に「入力ストリームバッファー」に保存され、Uploadサーバー上のメソッドでのみ使用できるようになると思います。実際、サーバー上のファイルのサイズが (予想どおり) 徐々に増加するのではなく、ファイルが作成された時点で既に 100MB の大きさになっていることがわかります。

質問:「入力ストリーム バッファ」が適度に小さく (たとえば 1MB)、オーバーフローしたときにUploadメソッドが呼び出されるようにするにはどうすればよいでしょうか? 言い換えれば、ファイル全体をどこにもバッファリングすることなく、アップロードを真にストリーミングしたいのです。

編集:httpRuntimeここに関連する別の設定が含まれ ていることを発見しました - requestLengthDiskThreshold。入力バッファがこのしきい値を超えて大きくなると、メモリではなくファイルシステムに保存されるようです。したがって、少なくとも 100MB の大きなファイル全体がメモリに保持されません (これが私が最も恐れていたことです) が、このバッファを完全に回避する方法があるかどうかを知りたいです。

4

2 に答える 2

9

.NET 4 を使用し、IIS7+ でサービスをホストしている場合、次のブログ投稿で説明されている ASP.NET バグの影響を受ける可能性があります。

http://blogs.microsoft.co.il/blogs/idof/archive/2012/01/17/what-s-new-in-wcf-4-5-improved-streaming-in-iis-hosting.aspx

基本的に、ストリーミングされた要求の場合、IIS の ASP.NET ハンドラーは、制御を WCF に渡す前に要求全体をバッファーします。そして、このハンドラーはmaxRequestLength制限に従います。

私の知る限り、バグの回避策はなく、次のオプションがあります。

  • .NET 4.5 へのアップグレード
  • IIS を使用する代わりにサービスを自己ホストする
  • ASP.NET ハンドラーが関与しないように、HTTP に基づかないバインディングを使用します。
于 2013-01-24T09:58:13.480 に答える
7

This may be a bug in the streaming implementation. I found a MSDN article that suggests doing exactly what you are describing at http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/fb9efac5-8b57-417e-9f71-35d48d421eb4/. Unfortunately the Microsoft employee suggesting the fix found a bug in the implementation and didn't follow up with details on a fix.

That said it looks like the implementation is broken which you could test by profiling your code with a memory profiler and verifying whether or not the entire file is being stored in memory. If the entire file is being stored in memory, you won't be able to fix this issue, unless somebody finds a configuration issue with your code.

That said, while using requestLengthDiskThreshold could technically work, it will dramatically increase your write times as each file will have to be written first as temp data, read from temp data, written again as final, and finally the temp data deleted. As you have already said you are dealing with extremely large files so I doubt such a solution is acceptable.

Your best bet is to use a chunking framework and manually reconstruct the file. I found instructions on how to write such logic at http://aspilham.blogspot.com/2011/03/file-uploading-in-chunks-using.html but have not had the time to check it for accuracy.

I'm sorry I can't tell you why your code isn't working as documented, but something similar to the 2nd example should be able to work without ballooning your memory footprint.

于 2013-01-23T06:10:20.680 に答える