C# 为什么这个布尔值不是线程安全的?

C# 为什么这个布尔值不是线程安全的?,c#,multithreading,thread-safety,C#,Multithreading,Thread Safety,我已经为一个集成测试编写了下面的代码,该测试为几个不同的程序集调用了一个昂贵的函数DoSomething()。我本以为应用锁就足以确保线程安全,但有时布尔值是真的,而在我当前的测试用例中它应该总是假的 我还尝试了Interlocked.CompareExchange的解决方案,它似乎对我也不起作用 有人能给我指出我到底做错了什么吗 public class SomeClass { private object _lock = new object(); private volat

我已经为一个集成测试编写了下面的代码,该测试为几个不同的程序集调用了一个昂贵的函数DoSomething()。我本以为应用锁就足以确保线程安全,但有时布尔值是真的,而在我当前的测试用例中它应该总是假的

我还尝试了Interlocked.CompareExchange的解决方案,它似乎对我也不起作用

有人能给我指出我到底做错了什么吗

public class SomeClass
{
    private object _lock = new object();
    private volatile bool _isSuccessful = true;

    private bool IsSuccesful
    {
        get
        {
            lock (_lock)
            {
                return _isSuccessful;
            }
        }
        set
        {
            lock (_lock)
            {
                _isSuccessful = value;
            }
        }
    }

    public bool Get()
    {
        Parallel.ForEach(..., ... =>
        {
            IsSuccesful = IsSuccesful & DoSomething();
        });

        return IsSuccesful;
    }
}
你说
DoSomething()
很贵(所以我猜很费时)

现在想象两个并行调用,一个是
DoSometing()
成功调用(A),另一个是失败调用(B)。这显然是可能发生的情况:

  • A检查
    issusccessful
    是否为
    true
    ,因此调用
    DoSomething
  • B检查
    issusccessful
    ,它仍然是
    true
    ,因此调用
    DoSomething
  • DoSomething
    为B返回
    false
    (好吧,它恰好快了一点),因此B将
    issessful
    设置为
    false
  • 现在
    DoSomething
    返回A的
    true
    。现在A有
    true和true
    (因为
    issusccessful
    读取时是
    true
    ),因此再次将
    issusccessful
    设置为
    true
  • 您面临的问题是,您使用不同的方法来检查值和设置值。这是一部经典之作

    我建议使用,但是在
    Parallel.ForEach
    内的单个
    int

    public bool Get()
    {
        int failed = 0;
        Parallel.ForEach(..., ... =>
        {   
            if (!DoSomething())         
                Interlocked.Increment(ref failed);
        });
    
        return failed == 0;
    }
    
    如果您只对其中一项测试失败感兴趣,您当然可以简单地执行以下操作:

    public bool Get()
    {
        Parallel.ForEach(..., ... =>
        {   
            if (!DoSomething()) IsSuccessful = false;
        });
    
        return IsSuccessful;
    }
    
    你说
    DoSomething()
    很贵(所以我猜很费时)

    现在想象两个并行调用,一个是
    DoSometing()
    成功调用(A),另一个是失败调用(B)。这显然是可能发生的情况:

  • A检查
    issusccessful
    是否为
    true
    ,因此调用
    DoSomething
  • B检查
    issusccessful
    ,它仍然是
    true
    ,因此调用
    DoSomething
  • DoSomething
    为B返回
    false
    (好吧,它恰好快了一点),因此B将
    issessful
    设置为
    false
  • 现在
    DoSomething
    返回A的
    true
    。现在A有
    true和true
    (因为
    issusccessful
    读取时是
    true
    ),因此再次将
    issusccessful
    设置为
    true
  • 您面临的问题是,您使用不同的方法来检查值和设置值。这是一部经典之作

    我建议使用,但是在
    Parallel.ForEach
    内的单个
    int

    public bool Get()
    {
        int failed = 0;
        Parallel.ForEach(..., ... =>
        {   
            if (!DoSomething())         
                Interlocked.Increment(ref failed);
        });
    
        return failed == 0;
    }
    
    如果您只对其中一项测试失败感兴趣,您当然可以简单地执行以下操作:

    public bool Get()
    {
        Parallel.ForEach(..., ... =>
        {   
            if (!DoSomething()) IsSuccessful = false;
        });
    
        return IsSuccessful;
    }
    
    这应该足够了。从文档:

    volatile修饰符通常用于用户访问的字段 不使用lock语句序列化访问的多线程

    它有效地序列化了访问

    尽管如此,我认为这不是问题所在 你说测试应该总是返回false,但有时返回true。为了“知道”它总是返回false,那么所有DoSomething()调用都必须返回false。这是真的吗?如果这是一个标准(串行)foreach,那么它将始终以相同的顺序执行,因此,一旦对DoSomething的调用返回false,isSuccessful将在迭代的其余部分保持false。当你运行这个并行程序时,命令就不存在了。最终得到的执行顺序每次都可能不同。这是由于DoSomething调用可能有不同的完成时间,并且每次都可以以不同的顺序运行。您的结果可能不可重复且不一致

    我想你真正想要的是一个Issuccessful,它只有在所有DoSomething返回true时才是真的。一种方法是删除&&并使用简单的if语句。例如:

    public bool Get()
    {
        var good = true;
        Parallel.ForEach(..., ... =>
        {
            var result = DoSomething();
            if (result == false) 
            {
                good = false;
            }
        });
        IsSuccesful = good;
        return IsSuccesful;
    }
    
    一旦“好”变为假,就没有方法将其恢复为真。因此,如果一个测试返回false,那么IsSuccessful将返回false。这也可能会让未来的开发人员更清楚地了解代码的意图。

    这就足够了。从文档:

    volatile修饰符通常用于用户访问的字段 不使用lock语句序列化访问的多线程

    它有效地序列化了访问

    尽管如此,我认为这不是问题所在 你说测试应该总是返回false,但有时返回true。为了“知道”它总是返回false,那么所有DoSomething()调用都必须返回false。这是真的吗?如果这是一个标准(串行)foreach,那么它将始终以相同的顺序执行,因此,一旦对DoSomething的调用返回false,isSuccessful将在迭代的其余部分保持false。当你运行这个并行程序时,命令就不存在了。最终得到的执行顺序每次都可能不同。这是由于DoSomething调用可能有不同的完成时间,并且每次都可以以不同的顺序运行。您的结果可能不可重复且不一致

    我想你真正想要的是一个Issuccessful,它只有在所有DoSomething返回true时才是真的。一种方法是删除&&并使用简单的if语句。例如:

    public bool Get()
    {
        var good = true;
        Parallel.ForEach(..., ... =>
        {
            var result = DoSomething();
            if (result == false) 
            {
                good = false;
            }
        });
        IsSuccesful = good;
        return IsSuccesful;
    }
    
    一旦“好”变为假,就没有方法将其恢复为真。因此,如果一个测试返回false,那么IsSuccessful将返回false。这也可能会让未来的开发人员更清楚地了解代码的意图。

    您能将其转化为v吗