Lock
物件
注意
本文是功能規格。 規格可作為功能的設計檔。 其中包含建議的規格變更,以及功能設計和開發期間所需的資訊。 這些文章會發佈,直到提議的規格變更完成並併併入目前的ECMA規格為止。
功能規格與已完成實作之間可能有一些差異。 這些差異是在 的相關語言設計會議(LDM)注意事項中擷取的。
冠軍號:https://github.com/dotnet/csharplang/issues/7104
總結
特殊案例 System.Threading.Lock
與 lock
關鍵詞的互動方式(其實質上是呼叫 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
語句
- 其中
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(); } } }
- 其中
x
是 reference_type的表達式,完全符合: [...]
請注意,圖形可能不會完全檢查(例如,如果 Lock
類型不是 sealed
,則不會有任何錯誤或警告),但功能可能無法如預期般運作(例如,將 Lock
轉換成衍生類型時不會有警告,因為功能假設沒有衍生類型)。
此外,在向上傳播 System.Threading.Lock
類型時,會將新的警告新增至隱含參考轉換 (10.2.8) 中:
隱含參考轉換如下:
- 從任何 reference_type 到
object
與dynamic
。
- 當已知 reference_type 是
System.Threading.Lock
時,會報告警告。- 從任何 class_type
S
到任何 class_typeT
,前提是S
衍生自T
。
- 已知
S
System.Threading.Lock
時會報告警告。- 從任何 class_type
S
到任何 interface_typeT
,條件是S
實作T
。
- 已知
S
System.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
s(因為現有的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
我們可以包含靜態分析,以防止使用
await
susing
s 中的System.Threading.Lock
。 亦即,我們可以針對類似using (lockVar.EnterScope()) { await ... }
的程式代碼發出錯誤或警告。 目前不需要這麼做,因為Lock.Scope
是ref 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.Enter
和Lock.Exit
方法,而不是使用ref struct Scope
。 不過,Exit
方法必須在從與Enter
不同的線程呼叫時擲回 ,因此它會包含使用Scope
時避免的線程查閱。 - 如果
using
主體內沒有await
,最好是在async
方法中,在ref struct
上編譯using
。
- 目前,由於
設計會議
-
LDM 2023-05-01:支援
lock
模式的初始決策 - LDM 2023-10-16:分配到 .NET 9 的工作集
-
LDM 2023-12-04:拒絕一般模式,只接受特殊大小寫
Lock
類型 + 新增靜態分析警告