0

Silverlight ビジネス アプリケーションを開発していて、1 つのファイルを 4096 KB のサイズのパーツに分割する「マルチパート」アップロードを実装したいと考えています。これらのパーツをクライアントからサーバーにアップロードするために、WebClient (クライアント側) と汎用ハンドラー (*.ashx、サーバー側) を使用しています。

戦略: 最初の部分で、Entity Framework クラスの新しいインスタンスが作成されます。このオブジェクトには、フィールド/プロパティ「バイナリ」があります (SQL では varbinary(MAX)、Entity Framework では byte[])。最初の部分を「binary」プロパティに保存し、SaveChanges() を実行します。次に、ハンドラーは、この新しいオブジェクトの ID (主キー) をクライアントに返します。

サーバーへの 2 番目の要求には、ファイルの 2 番目の部分のほかに、最初の要求の後に返された ID が含まれています。サーバー上で、以前に作成したオブジェクトをデータベースから読み込み、2 番目の部分を追加します。

myobject.binary = myobject.binary.Concat(bytes).ToArray<byte>();

myobjectは以前に作成したオブジェクトで、バイトはバイナリプロパティに追加する部分です。

ファイル全体がサーバーにアップロードされるまで、この「戦略」を繰り返します。これは、最大サイズが 78 MB までのファイルで問題なく機能します。サイズが ~83MB のファイルの場合、散発的に動作します。サイズが ~140MB のファイルは、SaveChanges() でOutOfMemory Exceptionで中止されます。

スタックトレース

at System.Object.MemberwiseClone()
at System.Array.Clone()
at System.Data.Common.CommandTrees.DbConstantExpression..ctor(TypeUsage resultType, Object value)
at System.Data.Mapping.Update.Internal.UpdateCompiler.GenerateValueExpression(EdmProperty property, PropagatorResult value)
at System.Data.Mapping.Update.Internal.UpdateCompiler.BuildSetClauses(DbExpressionBinding target, PropagatorResult row, PropagatorResult originalRow, TableChangeProcessor processor, Boolean insertMode, Dictionary`2& outputIdentifiers, DbExpression& returning, Boolean& rowMustBeTouched)
at System.Data.Mapping.Update.Internal.UpdateCompiler.BuildUpdateCommand(PropagatorResult oldRow, PropagatorResult newRow, TableChangeProcessor processor)
at System.Data.Mapping.Update.Internal.TableChangeProcessor.CompileCommands(ChangeNode changeNode, UpdateCompiler compiler)
at System.Data.Mapping.Update.Internal.UpdateTranslator.<ProduceDynamicCommands>d__0.MoveNext()
at System.Linq.Enumerable.<ConcatIterator>d__71`1.MoveNext()
at System.Data.Mapping.Update.Internal.UpdateCommandOrderer..ctor(IEnumerable`1 commands, UpdateTranslator translator)
at System.Data.Mapping.Update.Internal.UpdateTranslator.ProduceCommands()
at System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter)
at System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager entityCache)
at System.Data.Objects.ObjectContext.SaveChanges(SaveOptions options)
at MyObjectContext.SaveChanges(SaveOptions options) in PathToMyEntityModel.cs:Line 83.
at System.Data.Objects.ObjectContext.SaveChanges()
at MultipartUpload.ProcessRequest(HttpContext context) in PathToGenericHandler.ashx.cs:Line 73.

私の実装の何が問題なのですか?さらに詳しい情報やコード スニペットが必要な場合は、お知らせください。

敬具、クリス

4

1 に答える 1

5

考えてみてください。(たとえば) 130 MB をアップロードした後、次の行を実行するために必要なメモリ量:

myobject.binary = myobject.binary.Concat(bytes).ToArray<byte>();

明らかに、前の配列はメモリ内にあり、130 MB です。そして、どういうわけか、新しい配列もメモリ内にある必要があります。さらに 130 MB ですよね?

実際にはもっと悪いです。Concat()はシーケンスを生成しており、ToArray()それがどのくらい大きくなるかわかりません。

つまり.ToArray()、内部バッファを作成し、.Concat()イテレータからの出力で埋め始めるということです。明らかに、バッファの大きさがわからないため、時々、バッファが保持できるよりも多くのバイトが入ってくることがわかります。次に、より大きなバッファーを作成する必要があります。それが行うことは、前のバッファの 2 倍の大きさのバッファを作成し、すべてをコピーして、新しいバッファの使用を開始することです。しかし、それはある時点で、古いバッファと新しいバッファが同時にメモリ内になければならないことを意味します。

ある時点で、古いバッファーは 128 MB になり、新しいバッファーは 256 MB になります。古いファイルの 130 MB を合わせると、約 0.5 GB になります。ここで、2 人 (またはそれ以上) のユーザーが同時にこれを行わないようにしましょう。

別のメカニズムを使用することをお勧めします。たとえば、アップロードしたチャンクをディスク上の一時ファイルに保存します。新しいチャンクが入ってきたら、ファイルに追加するだけです。アップロードが完了したときにのみ、データベースに保存するなど、ファイルに対して行う必要があることをすべて実行します。

また、.NET の配列の最大サイズは 31 ビット インデックスによって制限されることに注意してください。したがって、システムに搭載されている RAM の量に関係なく、バイト配列の最大サイズは 2 GB です。

最後に、これほど大きなメモリ ブロックを扱っている場合は、.NET 4.5 のLarge Object Heap Improvementsを利用できるように、少なくとも .NET 4.5 で 64 ビット プロセスで実行していることを確認してください。しかし、 「メモリ不足」は物理メモリを参照していないため、それは魔法ではありません。

于 2013-10-16T13:12:27.800 に答える