I'm trying to figure out if .NET 4.0's Enum.TryParse is thread safe.
The source code (decompiled) is:
[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;
}
What seems problematic to me is this line:
result = default(TEnum); /// (*)
What if another thread accessed result right after it's been set to the default value, and before it is set to the parsed value?
[EDIT] Following Zoidberg's answer, I'd like to rephrase the question a bit.
The question is, I guess, if Enum.TryParse is "transactional" (or atomic).
Say I have a static field, and pass it into Enum.TryParse:
public static SomeEnum MyField;
....
Enum.TryParse("Value", out MyField);
Now, when TryParse is being executed, another thread accesses MyField. TryParse will change MyField's value to SomeEnum's default value for a while, and only then will set it to the parsed value.
This is not necessarily a bug in my code. I would expect Enum.TryParse to either set MyField to the parsed value or not touch it at all, not use it as its temporary field.
result
, as with every other local variable and parameter, is per-call. The thread-safety of a by-ref parameter is a little more complex to describe, but: in every sane usage - this will not be a problem. I could force a scenario where it was at-risk (due to by-ref passing), but it would be a contrived example.
Typical usage:
SomeEnumType foo;
if(Enum.TryParse(s, true, out foo)) {...}
Is perfectly safe.
The following is a bit more complex:
var objWithField = new SomeType();
// thread 1:
{
Enum.TryParse(x, true, out objWithField.SomeField));
}
// thread 2:
{
Enum.TryParse(y, true, out objWithField.SomeField));
}
and is not thread-safe, but for much more subtle reasons than those you are describing in the question.
Yes - it is.
There is no shared state at all in the method you have decompiled.
If another thread calls the same method, it gets it's own copy of all the locals and result
is passed in by the caller.
If result
had been a class level variable then this would have been an issue, but this is fine.
result (and thus the variable it refers to) can become default(T), although the string value holds a different enum value if it's that what you mean.
Try the following program:
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");
}
}
}
}
It will sooner or later (probably sooner) throw the "Not threadsafe" exception.
It really isn't a question of whether TryParse is threadsafe, but whether the code using it (your code) is thread safe. If you pass the result variable in, and then let it be accessed by another thread without some mutual exclusion protection, then you could have a problem, but the problem would be in your code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With