25

この質問は、ここにある別の投稿のコメントに関連しています:EntityFrameworkクエリのキャンセル

わかりやすくするために、そこからコード例を再現します。

    var thread = new Thread((param) =>
    {
        var currentString = param as string;

        if (currentString == null)
        {
            // TODO OMG exception
            throw new Exception();
        }

        AdventureWorks2008R2Entities entities = null;
        try // Don't use using because it can cause race condition
        {
            entities = new AdventureWorks2008R2Entities();

            ObjectQuery<Person> query = entities.People
                .Include("Password")
                .Include("PersonPhone")
                .Include("EmailAddress")
                .Include("BusinessEntity")
                .Include("BusinessEntityContact");
            // Improves performance of readonly query where
            // objects do not have to be tracked by context
            // Edit: But it doesn't work for this query because of includes
            // query.MergeOption = MergeOption.NoTracking;

            foreach (var record in query 
                .Where(p => p.LastName.StartsWith(currentString)))
            {
                // TODO fill some buffer and invoke UI update
            }
        }
        finally
        {
            if (entities != null)
            {
                entities.Dispose();
            }
        }
    });

thread.Start("P");
// Just for test
Thread.Sleep(500);
thread.Abort();

私は言うコメントを理解することはできません

競合状態を引き起こす可能性があるため、使用しないでください

entitiesはローカル変数であり、コードが別のスレッドで再入力された場合は共有されません。同じスレッド内で、「using」ステートメント内でコードを割り当てることは完全に安全であるように見えます(実際には指定されたコードと同等です)。 try / finalを使用して手動で処理するのではなく、通常の方法です。誰かが私を啓発できますか?

4

4 に答える 4

44

ええ、usingステートメントには競合の可能性があります。C#コンパイラは変換します

using (var obj = new Foo()) {
    // statements
}

に:

var obj = new Foo();
try {
   // statements
}
finally {
   if (obj != null) obj.Dispose();
}

競合は、 obj割り当てステートメントとtryブロックの間でスレッドが中止されたときに発生します。オッズは非常に小さいですが、ゼロではありません。その場合、オブジェクトは破棄されません。この競合が発生しないように、tryブロック内で割り当てを移動して、彼がそのコードをどのように書き直したかに注意してください。レースが発生したときに実際に根本的に問題が発生することはありません。オブジェクトを破棄する必要はありません。

スレッドアボートをわずかに効率的にするか、ステートメントを使用して手動で記述するかを選択する必要があるため、最初にThread.Abort()を使用する習慣を身に付けないことを選択する必要があります。実際にこれを行うことはお勧めできません。usingステートメントには、事故が発生しないようにするための追加の安全対策があり、 usingステートメント内でオブジェクトが再割り当てされた場合でも元のオブジェクトが確実に破棄されます。catch句を追加すると、事故が発生しにくくなります。usingステートメントは、バグの可能性を減らすために存在し、常にそれを使用します


この問題について少しうなずくと、答えは人気があります。まったく同じ人種に苦しむ別の一般的なC#ステートメントがあります。次のようになります。

lock (obj) {
    // statements
}

翻訳:

Monitor.Enter(obj);
// <=== Eeeek!
try {
    // statements
}
finally {
    Monitor.Exit(obj);
}

まったく同じシナリオで、スレッドアボートはEnter()呼び出しの後、tryブロックに入る前に発生する可能性があります。これにより、Exit()呼び出しが行われなくなります。これは、もちろん行われないDispose()呼び出しよりもはるかに厄介です。これにより、ほぼ確実にデッドロックが発生します。この問題はx64ジッターに固有のものであり、このひどい詳細はこのJoeDuffyブログ投稿で詳しく説明されています。

これを確実に修正することは非常に困難であり、Enter()呼び出しをtryブロック内に移動しても問題を解決できません。Enter呼び出しが行われたことを確認できないため、例外をトリガーせずにExit()メソッドを確実に呼び出すことはできません。Duffyが話していたMonitor.ReliableEnter()メソッドは最終的には発生しました。Monitorの.NET4バージョンは、ref bool lockTaken引数を取るTryEnter()オーバーロードを取得しました。これで、Exit()を呼び出しても問題ないことがわかりました。

さて、あなたが見ていない夜にBUMPになる恐ろしいもの。安全に割り込むことができるコードを書くのは難しいです。あなたが書いたのではないコードがこれらすべてを処理したと決して思い込まないのが賢明でしょう。レースは非常にまれであるため、このようなコードのテストは非常に困難です。あなたは決して確信することはできません。

于 2013-02-12T11:34:47.553 に答える
7

非常に奇妙なことに、原因usingはtry-finallyブロックのシンタックスシュガーだけです。

MSDNから:

オブジェクトをtryブロック内に配置し、finallyブロックでDisposeを呼び出すことで、同じ結果を得ることができます。実際、これはusingステートメントがコンパイラーによって変換される方法です。

于 2013-02-12T10:50:10.197 に答える
4

使用するusingか明示的に使用するかによってtry/finally、サンプル コードを使用してわずかに異なるコードを作成できます。

    AdventureWorks2008R2Entities entities = null;
    try // Don't use using because it can cause race condition
    {
        entities = new AdventureWorks2008R2Entities();
        ...
    } finally {
    } 

それをusingステートメントに置き換えると、次のようになります

   using(var entities = new AdventureWorks2008R2Entities()){
      ...
   }

仕様の §8.13 に従っているものは、次のように拡張されます。

    AdventureWorks2008R2Entities entities = new AdventureWorks2008R2Entities();
    try
    {
        ...
    } finally {
    } 

したがって、唯一の実際の違いは、代入が try/finally ブロック内にないことですが、競合状態が発生する可能性に影響を与えないことです (Hans も指摘しているように、代入と try ブロックの間のスレッドの中止は別として)。

于 2013-02-12T11:50:15.007 に答える
2

using ステートメントはコンパイラによって try/finally ブロックに変換されるため、このコメントは意味がありません。「エンティティ」はスコープ外では使用されないため、リソースを自動的に破棄する using ステートメントを使用する方が簡単です。

詳細については、MSDN: using Statement (C# Reference) を参照してください。

于 2013-02-12T10:58:00.093 に答える