次の方法で共有


Lock オブジェクト

この記事は機能仕様です。 仕様は、機能の設計ドキュメントとして機能します。 これには、提案された仕様の変更と、機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が最終決定され、現在の ECMA 仕様に組み込まれるまで公開されます。

機能の仕様と完成した実装の間には、いくつかの違いがある可能性があります。 これらの違いは、関連する 言語設計会議 (LDM) ノートでキャプチャされます。

機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。

チャンピオン号: https://github.com/dotnet/csharplang/issues/7104

概要

System.Threading.Locklock キーワードと対話する特殊なケース (内部でEnterScope メソッドを呼び出す)。 静的分析の警告を追加して、可能な限り型の誤用を防ぎます。

モチベーション

.NET 9 では、既存のモニター ベースのロックの代わりにSystem.Threading.Lock型が導入されています。 C# に lock キーワードが存在すると、開発者はこの新しい型で使用できる可能性があると考える可能性があります。 そうすることで、この型のセマンティクスに従ってロックされることはありませんが、代わりに他のオブジェクトとして扱われ、モニターベースのロックが使用されます。

namespace System.Threading
{
    public sealed class Lock
    {
        public void Enter();
        public void Exit();
        public Scope EnterScope();
    
        public ref struct Scope
        {
            public void Dispose();
        }
    }
}

詳細な設計

lock ステートメント (§13.13) のセマンティクスは、 System.Threading.Lock 型の特殊なケースに変更されます。

フォームの lock ステートメント lock (x) { ... }

  1. ここで、 xSystem.Threading.Lock型の式であり、次の式と正確に等しくなります。
    using (x.EnterScope())
    {
        ...
    }
    
    および System.Threading.Lock には次の図形が必要です。
    namespace System.Threading
    {
        public sealed class Lock
        {
            public Scope EnterScope();
    
            public ref struct Scope
            {
                public void Dispose();
            }
        }
    }
    
  2. ここで、 xreference_typeの式であり、正確には [...] と同じです。

図形が完全にチェックされていない可能性があることに注意してください (たとえば、 Lock 型が sealedされていない場合はエラーも警告も発生しません)、機能が期待どおりに動作しない可能性があります (たとえば、 Lock を派生型に変換する場合、その機能は派生型がないことを前提としているため、警告はありません)。

さらに、型をアップキャストするときに、暗黙的な参照変換 (System.Threading.Lock) に新しい警告が追加されます。

暗黙的な参照変換は次のとおりです。

  • 任意の reference_type から objectdynamicまで。
    • reference_typeSystem.Threading.Lockであることが判明すると、警告が報告されます。
  • Sから派生している場合、任意のclass_typeTから任意のSTに渡されます。
    • SSystem.Threading.Lockであることが判明すると、警告が報告されます。
  • 任意のclass_typeSから任意のinterface_typeTまで、Sを実装T提供されます。
    • SSystem.Threading.Lockであることが判明すると、警告が報告されます。
  • [...]
object l = new System.Threading.Lock(); // warning
lock (l) { } // monitor-based locking is used here

この警告は、同等の明示的な変換でも発生します。

コンパイラは、 objectに変換した後にインスタンスをロックできない場合に警告を報告することを回避します。

  • 変換が暗黙的であり、オブジェクト等価演算子の呼び出しの一部である場合。
var l = new System.Threading.Lock();
if (l != null) // no warning even though `l` is implicitly converted to `object` for `operator!=(object, object)`
    // ...

警告から抜け出し、モニターベースのロックを強制的に使用するには、

  • 通常の警告抑制手段 (#pragma warning disable)
  • Monitor API を直接、
  • object AsObject<T>(T l) => (object)l;などの間接キャスト。

選択肢

  • 他の型が lock キーワードとの対話に使用できる一般的なパターンをサポートします。 これは、 ref structがジェネリックに参加できる場合に実装される可能性のある将来の作業です。 LDM 2023-12-04 で説明

  • 既存のモニター ベースのロックと新しい Lock (または将来のパターン) の間のあいまいさを回避するには、次の方法があります。

    • 既存の lock ステートメントを再利用する代わりに、新しい構文を導入します。
    • 新しいロックの種類を structする必要があります (既存の lock では値型が許可されていないので)。 構造体に遅延初期化がある場合は、既定のコンストラクターとコピーに問題がある可能性があります。
  • codegen は、スレッドの中止 (それ自体は廃止されています) に対して強化される可能性があります。

  • また、 Lock が型パラメーターとして渡されたときにも警告する可能性があります。これは、型パラメーターのロックでは常にモニター ベースのロックが使用されるためです。

    M(new Lock()); // could warn here
    
    void M<T>(T x) // (specifying `where T : Lock` makes no difference)
    {
        lock (x) { } // because this uses Monitor
    }
    

    ただし、これにより、望ましくない Lockをリストに格納するときに警告が発生します。

    List<Lock> list = new();
    list.Add(new Lock()); // would warn here
    
  • System.Threading.Lockを使用してusingawaitを使用しないように静的分析を含めることができます。 つまり、 using (lockVar.EnterScope()) { await ... }などのコードに対してエラーまたは警告を出力できます。 現時点では、 Lock.Scoperef structであるため、これは必要ありません。そのため、コードは違法です。 しかし、ref structメソッドのasyncを許可したり、Lock.Scoperef structに変更したりすると、この分析が有益になります。 (今後実装される場合は、一般的なパターンと一致するすべてのロックの種類についても考慮する必要があります。一部のロックの種類は awaitで使用できる可能性があるため、オプトアウト メカニズムが必要になる場合があります)。または、ランタイムの一部として出荷されるアナライザーとして実装することもできます。

  • 値型を lockできないという制限を緩和できます

    • 新しい Lock 型 (API 提案によって class から struct に変更された場合にのみ必要)
    • は、将来実装されるときに任意の型が参加できる一般的なパターンです。
  • lockasync内で使用されないawaitメソッドで新しいlockを許可できます。

    • 現在、lockはリソースとしてusingを使用してref structに低下するため、コンパイル時エラーが発生します。 回避策は、 lock を別のasync 以外のメソッドに抽出することです。
    • ref struct Scopeを使用する代わりに、Lock.EnterLock.Exittryで/メソッドとfinallyメソッドを出力できます。 ただし、 Exit メソッドは、 Enterとは異なるスレッドから呼び出されたときにスローする必要があるため、 Scopeを使用するときに回避されるスレッド参照が含まれています。
    • using本体内にref structがない場合は、asyncメソッドのawaitusingをコンパイルできるようにすることが最善です。

デザインに関する会議

  • LDM 2023-05-01: lock パターンをサポートするための最初の決定
  • LDM 2023-10-16: .NET 9 のワーキング セットにトリアージ
  • LDM 2023-12-04: 一般的なパターンを拒否し、 Lock タイプの特殊な大文字と小文字の区別のみを受け入れ、静的分析警告を追加しました