共用方式為


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 (x) { ... }lock 語句

  1. 其中 x 是一種 System.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 類型時,會將新的警告新增至隱含參考轉換 (10.2.8) 中:

隱含參考轉換如下:

  • 從任何 reference_typeobjectdynamic
    • 當已知 reference_typeSystem.Threading.Lock時,會報告警告。
  • 從任何 class_typeS 到任何 class_typeT,前提是 S 衍生自 T
    • 已知 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 語句。
    • 需要新的鎖定類型 structs(因為現有的 lock 不允許實值型別)。 如果結構有延遲初始化,則預設建構函式和複製可能會發生問題。
  • 程序代碼根可以針對線程中止進行強化(這本身已經過時)。

  • 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
    
  • 我們可以包含靜態分析,以防止使用 awaits usings 中的 System.Threading.Lock。 亦即,我們可以針對類似 using (lockVar.EnterScope()) { await ... }的程式代碼發出錯誤或警告。 目前不需要這麼做,因為 Lock.Scoperef struct,因此無論如何,程式代碼都是非法的。 不過,如果我們在 async 方法中允許 ref struct,或將 Lock.Scope 變更為不是 ref struct,這項分析就會變得有益。 如果未來實作,我們也可能需要考慮這所有符合一般模式的鎖類型。雖然可能需要選擇退出機制,因為某些鎖類型可能會與 await搭配使用。或者,這可以作為運行時間一部分隨附的分析器。

  • 我們可以放寬實值型別不能 lock的限制

    • 針對新的 Lock 類型(只有在 API 提案從 class 變更為 struct時才需要)。
    • 適用於未來實作時,任何類型都可以參與的一般模式。
  • 我們可以允許在 async 方法中引入新的 lock,前提是 await 未在 lock內使用。

    • 目前,由於 lock 被降低為使用 ref struct 作為資源的 using,因此會產生編譯時錯誤。 因應措施是將 lock 提取到非async 的獨立方法中。
    • 我們可以在 try/finally中發出 Lock.EnterLock.Exit 方法,而不是使用 ref struct Scope。 不過,Exit 方法必須在從與 Enter不同的線程呼叫時擲回 ,因此它會包含使用 Scope時避免的線程查閱。
    • 如果 using 主體內沒有 await,最好是在 async 方法中,在 ref struct 上編譯 using

設計會議