【问题标题】:Enum.TryParse - is it thread safe?Enum.TryParse - 它是线程安全的吗?
【发布时间】:2012-03-02 13:08:52
【问题描述】:

我正在尝试确定 .NET 4.0 的 Enum.TryParse 是否是线程安全的。

源码(反编译)为:

[SecuritySafeCritical]
public static bool TryParse<TEnum>(string value, bool ignoreCase, out TEnum result) where TEnum : struct
{
    result = default(TEnum);   /// (*)
    Enum.EnumResult enumResult = default(Enum.EnumResult);
    enumResult.Init(false);
    bool result2;
    if (result2 = Enum.TryParseEnum(typeof(TEnum), value, ignoreCase, ref enumResult))
    {
        result = (TEnum)enumResult.parsedEnum;
    }
    return result2;
}

对我来说似乎有问题的是这一行:

result = default(TEnum);   /// (*)

如果另一个线程在它被设置为默认值之后并且在它被设置为解析值之前访问结果怎么办?

[编辑] 在 Zoidberg 的回答之后,我想重新表述一下这个问题。

问题是,我猜,如果 Enum.TryParse 是“事务性的”(或原子的)。

假设我有一个静态字段,并将其传递给 Enum.TryParse:

public static SomeEnum MyField;
....
Enum.TryParse("Value", out MyField);

现在,当执行 TryParse 时,另一个线程访问 MyField。 TryParse 会暂时将 MyField 的值更改为 SomeEnum 的默认值,然后才会将其设置为解析后的值。

这不一定是我的代码中的错误。我希望 Enum.TryParse 将 MyField 设置为解析值或根本不触摸它,不将其用作其临时字段。

【问题讨论】:

  • 其他 .NET TryParse 方法似乎也将您的 ref 变量用于自己的临时存储。这不应该被认为是微软代码中的一个错误吗?当您要求将变量设置为一个值时,您通常不会期望它在设置之前经历许多其他状态。
  • 它是否被记录为线程安全的?如果不是,那么不管它是否真的是线程安全的,你都不应该依赖它是线程安全的。

标签: c# .net .net-4.0 enums tryparse


【解决方案1】:

result 与所有其他局部变量和参数一样,是每次调用的。 by-ref 参数的线程安全性有点难以描述,但是:在每个理智的使用中 - 这都不是问题。我可以强制设置一个存在风险的场景(由于通过引用传递),但这将是一个人为的例子。

典型用法:

SomeEnumType foo;
if(Enum.TryParse(s, true, out foo)) {...}

绝对安全。

下面的有点复杂:

var objWithField = new SomeType();
// thread 1:
{
    Enum.TryParse(x, true, out objWithField.SomeField));
}
// thread 2:
{
    Enum.TryParse(y, true, out objWithField.SomeField));
}

并且不是线程安全的,但出于比您在问题中描述的更微妙的原因。

【讨论】:

    【解决方案2】:

    是的 - 是的。

    你反编译的方法根本没有共享状态。

    如果另一个线程调用相同的方法,它会得到它自己的所有局部变量的副本,并且result 由调用者传入。

    如果 result 是一个类级别的变量,那么这将是一个问题,但这很好。

    【讨论】:

    • 警告; result 是 by-ref,因此虽然指针的本地副本确实是隔离的,但底层值是 by-ref 并且存在风险,但仅在非常人为的示例中。
    • 我同意你的观点。请注意,在这种情况下 - result 是一个结构,所以这意味着您无论如何都不能改变任何其他人的副本,不是吗?
    • 因为它是 by-ref (out) 正是我们可以改变其他所有人的版本:只有一个值(通过-ref 传递的结构不会被复制; 而是发送一个指向该值的指针)
    • 啊,是的,当然。特别是因为它正在通过引用传递。
    【解决方案3】:

    结果(以及它所引用的变量)可以成为默认值(T),尽管字符串值持有不同的枚举值,如果这就是你的意思。

    试试下面的程序:

    public enum FooBar
    {
        Foo = 0,
        Bar
    }
    
    internal class Program
    {
        private static FooBar fb = FooBar.Bar;
    
        private static void Main()
        {
            new Thread(() =>
                           {
                               while (true)
                               {
                                   if (Program.fb == FooBar.Foo) // or try default(FooBar), which is the same
                                   {
                                       throw new Exception("Not threadsafe");
                                   }
                               }
                           }).Start();
    
            while (true)
            {
                if (!Enum.TryParse("Bar", true, out fb) || fb == FooBar.Foo)
                {
                    throw new Exception("Parse error");
                }
            }
        }
    }
    

    它迟早会(可能更早)抛出“非线程安全”异常。

    【讨论】:

      【解决方案4】:

      这真的不是 TryParse 是否是线程安全的问题,而是使用它的代码(您的代码)是否是线程安全的问题。如果你将结果变量传入,然后让另一个线程访问它而没有一些互斥保护,那么你可能会遇到问题,但问题出在你的代码中。

      【讨论】:

      • 这就是我的问题的真正意思——我会尝试修改这个问题。谢谢。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-19
      • 2011-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多