簡単なコンソール アプリケーションの例で問題を説明しようとしますが、実際のプロジェクトは ASP.NET MVC3 アプリケーションです。
次のテーブルがあります。
次のシナリオを想像してください。
- ユーザーがレポートを作成します (はレポートの内容、
TestReport
はフラグで、レポートを処理する準備ができているかどうかを示します)。デフォルトでは に設定されています。つまり、準備ができていません。Text
string
Ready
bool
Ready
false
- ユーザーはレポートを処理したいので、レポートを送信します。
Ready
ここに設定されてtrue
います。
レポートがまだ処理されていない場合、システムはレポートを取り消す機会を与えます。そのため、レポートがリコールされると、バックReady
に設定されfalse
ます。逆に、レポートが処理されると、TestReportRef
その によってレポートを参照する の行Id
が作成されます。
同時に想像してみてください
- ユーザーはレポートを取り消したいと考えています。
- レポートがプロセス リストに追加されます。
これが同時に発生するとすぐに、エラーが発生する可能性があります。つまり、レポートにはReady
==が含まfalse
れ、 で参照されTestReportRef
ます。
これがどのように発生するかを示す簡単なコンソールの例を次に示します。
var dc = new TestDataContext('my connection string');
dc.TestReport.InsertOnSubmit(new TestReport
{
Text = "My report content",
Ready = true //ready at once
});
dc.SubmitChanges();
Action recallReport = () =>
{
var _dc = new TestDataContext(cs);
var report = _dc.TestReport.FirstOrDefault(t => t.Ready);
if (report != null && !report.TestReportRef.Any())
{
Thread.Sleep(1000);
report.Ready = false;
_dc.SubmitChanges();
}
};
Action acceptReport = () =>
{
var _dc = new TestDataContext(cs);
var report = _dc.TestReport.FirstOrDefault(t => t.Ready);
if (report != null && !report.TestReportRef.Any())
{
Thread.Sleep(1000);
_dc.TestReportRef.InsertOnSubmit(new TestReportRef
{
FK_ReportId = report.Id
});
_dc.SubmitChanges();
}
};
var task1 = new Task(recallReport);
var task2 = new Task(acceptReport);
task1.Start();
task2.Start();
task1.Wait();
task2.Wait();
foreach (var t in dc.TestReport)
{
Console.WriteLine(string.Format("{0}\t{1}\t{2}", t.Id, t.Text, t.Ready));
}
foreach (var t in dc.TestReportRef)
{
Console.WriteLine("ref id:\t" + t.FK_ReportId);
}
Thread.Sleep(1000);
タスクが同じ状況を確実にチェックするように追加されます。
与えられた例はぎこちなく聞こえるかもしれませんが、私が扱っている問題を説明するはずです。
どうすればこれを回避できますか? リポジトリをシングルトンにするのは良い考えではないようです。共有ミューテックス (すべての Web 要求に 1 つ) を使用して、書き込み操作のみを分離しますか? または、この種のシナリオで使用する必要があるパターンはありますか?
これは、私が持っているシナリオの 1 つの単純化された例にすぎません。ただし、同様の不一致が発生するシナリオがいくつかあります。このような交差点を不可能にするのが最善だと思います。