2

任意の大きなファイルセットに対して、任意のコストのかかる作業を実行したいと思います。進捗状況をリアルタイムで報告し、すべてのファイルが処理された後に結果を表示したいと思います。私の表現に一致するファイルがない場合は、エラーをスローしたいと思います。

すべてのテストファイルをロードし、それらを(順不同で)実行し、進行状況をリアルタイムでレポートし、すべてのテストが完了した後に集計結果を表示するテストフレームワークを作成することを想像してみてください。

このコードをブロッキング言語(たとえばRubyなど)で記述するのは非常に簡単です。

結局のところ、この一見単純なタスクをノードで実行するのに問題がありますが、非同期のイベントベースのIOを実際に利用しています。

私の最初の設計は、各ステップを連続して実行することでした。

  1. すべてのファイルをロードし、処理するファイルのコレクションを作成します
  2. コレクション内の各ファイルを処理します
  3. すべてのファイルが処理されたら結果を報告します

このアプローチは機能しますが、プログラムの計算コストの高い部分がすべてのファイルIOの完了を待機するため、私には適切ではないようです。これは、ノードが回避するように設計された種類の待機ではありませんか?

私の2番目の設計は、ディスク上で非同期的に検出された各ファイルを処理することでした。議論のために、次のようなメソッドを想像してみましょう。

eachFileMatching(path, expression, callback) {
  // recursively, asynchronously traverse the file system,
  // calling callback every time a file name matches expression.
}

そして、このメソッドのコンシューマーは次のようになります。

eachFileMatching('test/', /_test.js/, function(err, testFile) {
  // read and process the content of testFile
});

この設計はIOを操作する非常に「ノード」の方法のように感じますが、2つの大きな問題があります(少なくとも私のおそらく誤った実装では)。

  1. すべてのファイルがいつ処理されたかわからないので、いつ結果をアセンブルして公開するかわかりません。
  2. ファイルの読み取りは非ブロッキングで再帰的であるため、ファイルが見つからなかったかどうかを知る方法に苦労しています。

私は単に何か間違ったことをしていること、そして他の人々が2番目のアプローチを機能させるために使用する合理的に単純な戦略があることを望んでいます。

この例ではテストフレームワークを使用していますが、これとまったく同じ問題にぶつかる他のさまざまなプロジェクトがあります。ノード内のファイルシステムにアクセスするかなり洗練されたアプリケーションを作成する人もいると思います。

4

3 に答える 3

1

「testFileのコンテンツを読み取って処理する」とはどういう意味ですか?

すべてのファイルがいつ処理されるのかわからない理由がわかりません。Streamsを使用していませんか?ストリームには、だけでなく、いくつかのイベントがありますdata。イベントを処理するendと、各ファイルがいつ終了したかがわかります。

たとえばlist、ファイル名があり、各ファイルの処理を設定し、endイベントを取得したら、リストからファイル名を削除します。リストが空になると完了です。または、名前と完了ステータスを含むFileNameオブジェクトを作成します。イベントを取得したらend、ステータスを変更し、ファイル名カウンターもデクリメントします。カウンターがゼロになったら完了です。自信がない場合は、すべてのFileNameオブジェクトをスキャンして、ステータスが完了していることを確認できます。

カウンターを定期的にチェックするタイマーがある場合もあります。一定期間カウンターが変更されない場合は、ステータスが完了していないFileNameオブジェクトで処理がスタックしている可能性があることを報告してください。

...私は別の質問でこのシナリオに出くわしましたが、受け入れられた回答(およびgithubリンク)がそれをうまく説明しています。ループオーバーイベント駆動型コードをチェックしてください。

于 2011-02-24T20:40:06.973 に答える
1

結局のところ、私が構築できた最小の実用的なソリューションは、私が思っていたよりもはるかに複雑です。

以下は私のために働くコードです。それはおそらくクリーンアップされるか、あちこちで少し読みやすくすることができます、そして私はそのようなフィードバックには興味がありません。

この問題を解決するための大幅に異なる方法、つまりより簡単で効率的な方法がある場合、私はそれを聞くことに非常に興味があります。この一見単純な要件の解決策が非常に大量のコードを必要とすることは本当に驚きですが、おそらくそれが誰かがioのブロックを発明した理由ですか?

複雑さは、実際には次のすべての要件を満たすことを望んでいます。

  • 見つかったファイルを処理する
  • 検索がいつ完了するかを知る
  • ファイルが見つからないかどうかを確認する

コードは次のとおりです。

/**
 * Call fileHandler with the file name and file Stat for each file found inside
 * of the provided directory.
 *
 * Call the optionally provided completeHandler with an array of files (mingled
 * with directories) and an array of Stat objects (one for each of the found
 * files.
 *
 * Following is an example of a simple usage:
 *
 *   eachFileOrDirectory('test/', function(err, file, stat) {
 *     if (err) throw err;
 *     if (!stat.isDirectory()) {
 *       console.log(">> Found file: " + file);
 *     }
 *   });
 *
 * Following is an example that waits for all files and directories to be 
 * scanned and then uses the entire result to do something:
 *
 *   eachFileOrDirectory('test/', null, function(files, stats) {
 *     if (err) throw err;
 *     var len = files.length;
 *     for (var i = 0; i < len; i++) {
 *       if (!stats[i].isDirectory()) {
 *         console.log(">> Found file: " + files[i]);
 *       }
 *     }
 *   });
 */
var eachFileOrDirectory = function(directory, fileHandler, completeHandler) {
  var filesToCheck = 0;
  var checkedFiles = [];
  var checkedStats = [];

  directory = (directory) ? directory : './';

  var fullFilePath = function(dir, file) {
    return dir.replace(/\/$/, '') + '/' + file;
  };

  var checkComplete = function() {
    if (filesToCheck == 0 && completeHandler) {
      completeHandler(null, checkedFiles, checkedStats);
    }
  };

  var onFileOrDirectory = function(fileOrDirectory) {
    filesToCheck++;
    fs.stat(fileOrDirectory, function(err, stat) {
      filesToCheck--;
      if (err) return fileHandler(err);
      checkedFiles.push(fileOrDirectory);
      checkedStats.push(stat);
      fileHandler(null, fileOrDirectory, stat);
      if (stat.isDirectory()) {
        onDirectory(fileOrDirectory);
      }
      checkComplete();
    });
  };

  var onDirectory = function(dir) {
    filesToCheck++;
    fs.readdir(dir, function(err, files) {
      filesToCheck--;
      if (err) return fileHandler(err);
      files.forEach(function(file, index) {
        file = fullFilePath(dir, file);
        onFileOrDirectory(file);
      });
      checkComplete();
    });
  }

  onFileOrDirectory(directory);
};
于 2011-02-27T03:07:54.667 に答える
0

これを行う2つの方法、最初に、おそらく連続して検討すると、次のようになります

var files = [];
doFile(files, oncomplete);

function doFile(files, oncomplete) {
  if (files.length === 0) return oncomplete();
  var f = files.pop();
  processFile(f, function(err) {
    // Handle error if any
    doFile(files, oncomplete); // Recurse
  });
};

function processFile(file, callback) {
  // Do whatever you want to do and once 
  // done call the callback
  ...
  callback();
};

第二に、それを並列と呼びましょう。これは似ており、summinは次のようになります。

var files = [];
doFiles(files, oncomplete);

function doFiles(files, oncomplete) {
  var exp = files.length;
  var done = 0;
  for (var i = 0; i < exp; i++) {
    processFile(files[i], function(err) {
      // Handle errors (but still need to increment counter)
      if (++done === exp) return oncomplete();      
    });
  }
};

function processFile(file, callback) {
  // Do whatever you want to do and once 
  // done call the callback
  ...
  callback();
};

ここで、2番目のアプローチを使用する必要があることは明らかなように思われるかもしれませんが、IOを多用する操作では、並列化時にパフォーマンスが実際に向上しないことがわかります。最初のアプローチの欠点の1つは、再帰によってスタックトレースが吹き飛ばされる可能性があることです。

Tnx

Guido

于 2011-02-25T00:34:32.420 に答える