16

WCF を使用して、信頼性の低い接続を介してコンピューター間で大きなファイルを転送する必要があります。

ファイルを再開できるようにしたいのですが、WCF によってファイルサイズが制限されたくないので、ファイルを 1 MB にチャンクしています。これらの「チャンク」はストリームとして転送されます。これまでのところ、これは非常にうまく機能します。

私の手順は次のとおりです。

  1. ファイルストリームを開く
  2. ファイルからバイト[]にチャンクを読み取り、メモリストリームを作成します
  3. 転送チャンク
  4. 2.に戻り、ファイル全体が送信されるまで

私の問題はステップ 2 にあります。バイト配列からメモリ ストリームを作成すると、最終的に LOH になり、メモリ不足の例外が発生すると思います。私は実際にこのエラーを作成できませんでした。私の仮定が間違っている可能性があります。

ここで、メッセージで byte[] を送信したくありません。WCF から配列のサイズが大きすぎることが通知されるからです。最大許容配列サイズおよび/またはチャンクのサイズを変更できますが、別の解決策があることを願っています。

私の実際の質問:

  • 私の現在のソリューションは LOH にオブジェクトを作成しますか?それによって問題が発生しますか?
  • これを解決するより良い方法はありますか?

ところで、受信側では、受信したストリームから小さなチャンクを単純に読み取り、ファイルに直接書き込むので、大きなバイト配列は必要ありません。

編集:

現在の解決策:

for (int i = resumeChunk; i < chunks; i++)
{
 byte[] buffer = new byte[chunkSize];
 fileStream.Position = i * chunkSize;
 int actualLength = fileStream.Read(buffer, 0, (int)chunkSize);
 Array.Resize(ref buffer, actualLength);
 using (MemoryStream stream = new MemoryStream(buffer)) 
 {
  UploadFile(stream);
 }
}
4

4 に答える 4

39

これで大丈夫だといいのですが。StackOverflowに関する私の最初の答えです。

はい、絶対にチャンクサイズが85000バイトを超える場合、配列はラージオブジェクトヒープに割り当てられます。すべて同じサイズのメモリの連続する領域の割り当てと割り当て解除を行っているため、メモリがすぐに不足することはないでしょう。そのため、メモリがいっぱいになると、ランタイムは新しいチャンクを古い再利用されたメモリ領域に収めることができます。

別の配列が作成されるため、Array.Resize呼び出しについて少し心配します(http://msdn.microsoft.com/en-us/library/1ffy6686(VS.80).aspxを参照)。これは、actualLength == Chunksizeの場合、最後のチャンクを除くすべてのチャンクの場合と同様に、不要な手順です。だから私は少なくとも提案します:

if (actualLength != chunkSize) Array.Resize(ref buffer, actualLength);

これにより、多くの割り当てが削除されます。actualSizeがchunkSizeと同じではないが、それでも85000を超える場合、新しい配列もラージオブジェクトヒープに割り当てられ、フラグメント化を引き起こし、明らかなメモリリークを引き起こす可能性があります。リークが非常に遅いため、実際にメモリが不足するまでにはまだ長い時間がかかると思います。

より良い実装は、ある種のバッファプールを使用して配列を提供することだと思います。あなたはあなた自身を転がすことができます(それはあまりにも複雑になるでしょう)が、WCFはあなたにそれを提供します。私はそれを利用するためにあなたのコードを少し書き直しました:

BufferManager bm = BufferManager.CreateBufferManager(chunkSize * 10, chunkSize);

for (int i = resumeChunk; i < chunks; i++)
{
    byte[] buffer = bm.TakeBuffer(chunkSize);
    try
    {
        fileStream.Position = i * chunkSize;
        int actualLength = fileStream.Read(buffer, 0, (int)chunkSize);
        if (actualLength == 0) break;
        //Array.Resize(ref buffer, actualLength);
        using (MemoryStream stream = new MemoryStream(buffer))
        {
            UploadFile(stream, actualLength);
        }
    }
    finally
    {
        bm.ReturnBuffer(buffer);
    }
}

これは、UploadFileの実装がnoのintを取るように書き直すことができることを前提としています。書き込むバイト数。

これがお役に立てば幸いです

ジョー

于 2010-05-18T13:29:26.757 に答える
2

あなたの質問の最初の部分についてはよくわかりませんが、より良い方法については、BITSを検討しましたか? http 経由でファイルをバックグラウンドでダウンロードできます。http:// または file:// URI を指定できます。中断された時点から再開可能であり、http HEADER の RANGE メソッドを使用してバイトのチャンクでダウンロードします。これは Windows Update によって使用されます。進行状況と完了に関する情報を提供するイベントをサブスクライブできます。

于 2010-05-12T13:31:45.950 に答える
1

私はこれに対する別の解決策を思いついたので、あなたの考えを教えてください!

メモリに大量のデータを保持したくないので、バイト配列またはストリームを一時的に格納するエレガントな方法を探していました。

アイデアは、一時ファイルを作成し (これを行うために特定の権限は必要ありません)、それをメモリ ストリームのように使用することです。クラスを Disposable にすると、使用後に一時ファイルがクリーンアップされます。

public class TempFileStream : Stream
{
  private readonly string _filename;
  private readonly FileStream _fileStream;

  public TempFileStream()
  {
     this._filename = Path.GetTempFileName();
     this._fileStream = File.Open(this._filename, FileMode.OpenOrCreate, FileAccess.ReadWrite);
  }

  public override bool CanRead
  {
   get
    {
    return this._fileStream.CanRead;
    }
   }

// and so on with wrapping the stream to the underlying filestream

...

    // finally overrride the Dispose Method and remove the temp file     
protected override void Dispose(bool disposing)
  {
      base.Dispose(disposing);

  if (disposing)
  {
   this._fileStream.Close();
   this._fileStream.Dispose();

   try
   {
      File.Delete(this._filename);
   }
   catch (Exception)
   {
     // if something goes wrong while deleting the temp file we can ignore it.
   }
  }
于 2012-07-03T08:39:56.983 に答える