16

ディレクトリを監視し、ファイルがドロップされたときにファイルを処理し、処理が完了するとすぐに処理されたファイルを削除(または移動)するクラスをC#で実装しています。このコードを実行する複数のスレッドが存在する可能性があるため、ファイルを取得した最初のスレッドが排他的にロックするため、他のスレッドが同じファイルを読み取ったり、外部プロセスやユーザーがアクセスしたりすることはできません。ファイルが削除/移動されるまでロックを保持したいので、別のスレッド/プロセス/ユーザーがアクセスするリスクはありません。

これまでのところ、2 つの実装オプションを試しましたが、いずれも希望どおりに機能しません。

オプション1

FileStream fs = file.Open(FileMode.Open, FileAccess.Read, FileShare.Delete);
//Read and process
File.Delete(file.FullName); //Or File.Move, based on a flag
fs.Close();

オプション 2

FileStream fs = file.Open(FileMode.Open, FileAccess.Read, FileShare.None);
//Read and process
fs.Close();
File.Delete(file.FullName); //Or File.Move, based on a flag

オプション 1の問題は、ファイルが完全にロックされているはずなのに、他のプロセスがファイルにアクセスできる (削除、移動、名前変更できる) ことです。

オプション 2の問題は、ファイルが削除される前にロックが解除されるため、削除が行われる前に他のプロセス/スレッドがファイルをロックできるため、削除が失敗することです。

すでに排他アクセスを持っているファイル ハンドルを使用して削除を実行できる API を探していました。

編集

監視対象のディレクトリは pub 共有にあるため、他のユーザーとプロセスがアクセスできます。 問題は、自分のプロセス内でロックを管理していません。私が解決しようとしている問題は、ファイルを排他的にロックし、ロックを解除せずに移動/削除する方法です

4

6 に答える 6

7

2つの解決策が思い浮かびます。

最初の最も簡単な方法は、スレッドにファイルの名前を他のスレッドが触れない名前に変更させることです。filename.dat.<unique number>" " のようなもので、<unique number>はスレッド固有のものです。その後、スレッドは必要なだけファイルをパーティーできます。

2 つのスレッドが同時にファイルを取得した場合、そのファイルの名前を変更できるのはそのうちの 1 つだけです。他のスレッドで発生する IOException を処理する必要がありますが、問題にはなりません。

もう 1 つの方法は、単一のスレッドでディレクトリを監視し、ファイル名をBlockingCollection. ワーカー スレッドは、そのキューから項目を取得して処理します。その特定の項目をキューから取得できるのは 1 つのスレッドだけなので、競合は発生しません。

このBlockingCollectionソリューションは、セットアップが少し (ほんの少しだけ) 複雑ですが、複数のスレッドが同じディレクトリを監視するソリューションよりも優れたパフォーマンスを発揮するはずです。

編集

あなたの編集された質問は、問題をかなり変えます。一般にアクセス可能なディレクトリにファイルがある場合、そこに配置されてからスレッドがロックするまでの任意の時点で、ファイルが表示、変更、または削除されるリスクがあります。

ファイルを開いている間はファイルを移動したり削除したりできないため (私が認識していません)、最善の策は、スレッドにファイルを公開されていないディレクトリに移動させることです。理想的には、アプリケーションを実行しているユーザーのみがアクセスできるようにロックダウンされたディレクトリです。したがって、コードは次のようになります。

File.Move(sourceFilename, destFilename);
// the file is now in a presumably safe place.
// Assuming that all of your threads obey the rules,
// you have exclusive access by agreement.

編集#2

別の可能性は、ファイルを排他的に開き、独自のコピー ループを使用してコピーし、コピーが完了したときにファイルを開いたままにすることです。その後、ファイルを巻き戻して処理を行うことができます。何かのようなもの:

var srcFile = File.Open(/* be sure to specify exclusive access */);
var destFile = File.OpenWrite(/* destination path */);
// copy the file
var buffer = new byte[32768];
int bytesRead = 0;
while ((bytesRead = srcFile.Read(buffer, 0, buffer.Length)) != 0)
{
    destFile.Write(buffer, 0, bytesRead);
}
// close destination
destFile.Close();
// rewind source
srcFile.Seek(0, SeekOrigin.Start);
// now read from source to do your processing.
// for example, to get a StreamReader, just pass the srcFile stream to the constructor.

場合によっては、処理してからコピーできます。処理が終了したときにストリームが開いたままかどうかによって異なります。通常、コードは次のようなことを行います。

using (var strm = new StreamReader(srcStream, ...))
{
    // do stuff here
}

これにより、ストリームと srcStream が閉じられます。次のようにコードを書く必要があります。

using (var srcStream = new FileStream( /* exclusive access */))
{
    var reader = new StreamReader(srcStream, ...);
    // process the stream, leaving the reader open
    // rewind srcStream
    // copy srcStream to destination
    // close reader
}

実行可能ですが、不器用です。

ああ、ファイルを削除する前に誰かがファイルを読み取る可能性を排除したい場合は、ファイルを閉じる前に 0 で切り捨ててください。次のように:

srcStream.Seek(0, SeekOrigin.Begin);
srcStream.SetLength(0);

そうすれば、削除する前に誰かがそれに到達した場合でも、変更する必要はありません。

于 2013-03-26T04:44:13.087 に答える
4

ファイルシステム自体は本質的に揮発性であるため、やりたいことを試すのは非常に困難です。これは、ファイル システムにおける典型的な競合状態です。オプション 2 では、作業を行う前に作成した「処理中」またはステージング ディレクトリにファイルを移動することもできます。パフォーマンスについては YMMV ですが、少なくともベンチマークを行って、ニーズに適合するかどうかを確認できます。

于 2013-03-26T03:43:16.420 に答える
3

生成スレッドから共有/同期されたリストの何らかの形式を実装する必要がある場合があります。親スレッドがディレクトリを定期的にチェックしてファイルを追跡している場合、ファイルを子スレッドに渡すことができ、ロックの問題が解消されます。

于 2013-03-26T03:40:17.417 に答える
-1

MoveFileExAPI 関数を使用して、次回の再起動時にファイルを削除するようにマークすることができます。ソース

于 2013-03-26T03:45:00.393 に答える