Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/310.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用装箱值类型作为lock语句的锁是否安全?_C#_.net_Locking_Value Type - Fatal编程技术网

C# 使用装箱值类型作为lock语句的锁是否安全?

C# 使用装箱值类型作为lock语句的锁是否安全?,c#,.net,locking,value-type,C#,.net,Locking,Value Type,该语句的文档非常简单: 其中x是引用类型的表达式 因此,不应允许我将值类型作为锁的locker传递给。不过我注意到我可以使用实现接口的值类型。换句话说,我可以做到: IDisposable locker = default(DisposableStruct); lock (locker) Console.WriteLine("Thread safe"); struct DisposableStruct : IDisposable { public void Dispose() { }

该语句的文档非常简单:

其中x是引用类型的表达式

因此,不应允许我将值类型作为
锁的locker传递给
。不过我注意到我可以使用实现接口的值类型。换句话说,我可以做到:

IDisposable locker = default(DisposableStruct);
lock (locker) Console.WriteLine("Thread safe");

struct DisposableStruct : IDisposable
{
    public void Dispose() { }
}
这是令人惊讶的。我发现原因是值类型是装箱的。根据报告:

装箱是将值类型转换为类型
对象
,或转换为该值类型实现的任何接口类型的过程

我的问题是,使用装箱值类型作为
lock
语句的储物柜是否有任何注意事项。引用类型包装器是否有可能在程序执行期间发生某种变化,从而导致线程安全代码失败


更新:下面是一个让我担心的例子。如果我运行下面的代码一百万次,是否可以保证始终显示正确的输出(100000000)

IComparable<int> boxedValueType = 0;
int sharedState = 0;
var tasks = Enumerable.Range(0, 10).Select(_ => Task.Run(() =>
{
    for (int i = 0; i < 100_000_000; i++)
        lock (boxedValueType)
            sharedState++;
})).ToArray();
Task.WaitAll(tasks);
Console.WriteLine(sharedState);
i可比较的boxedValueType=0;
int sharedState=0;
var tasks=Enumerable.Range(0,10)。选择(=>Task.Run(()=>
{
对于(int i=0;i<100_000;i++)
锁(BoxedValue类型)
sharedState++;
})).ToArray();
Task.WaitAll(任务);
Console.WriteLine(共享状态);

我认为您在过度思考
语句。是的,这是肯定的。否则就不可能了——这意味着对对象的托管引用会突然更改其存储值。运行时没有实现这一点的机制,它会破坏通过引用测试对象相等性的代码(以及其他许多东西)。一旦您拥有了
IComparable
并将其保持不变,它就和任何其他引用对象一样是一个可以锁定的好对象。这种方法的主要问题/弱点当然是,很容易在无意中创建不同的装箱实例并将其锁定在其他地方。如果您控制了所有代码(装箱和锁定的代码),那么您可以避免被咬,但由于很容易无意中创建值类型的副本,并且很容易隐式装箱,因此即使在安全的情况下也很危险。每次我都会接受一个
新对象()
的“开销”,因为这是一个明显正确的模式,而不是明显不正确的模式。@TheodorZoulias我说的不是
IEnumerable
引用的
IEnumerator
。我认为引用类型的任何实例方法都有隐式的
this
参数,因此可以执行
lock(this)
。因此,如果您调用引用类型的任何实例方法(如果它不是普通的,则可以包含实例构造函数),那么您不是该引用的唯一所有者。当引用是私有对象时,意外地公开它要困难得多,因为它不用于任何其他用途,只用于锁定。我也看不出为什么装箱比
newobject()
占用的资源少。是的,我同时查找了它。事实上没有问题。
IComparable<int> boxedValueType = 0;
int sharedState = 0;
var tasks = Enumerable.Range(0, 10).Select(_ => Task.Run(() =>
{
    for (int i = 0; i < 100_000_000; i++)
        lock (boxedValueType)
            sharedState++;
})).ToArray();
Task.WaitAll(tasks);
Console.WriteLine(sharedState);