2

Is this code thread safe?

public class SomeType
{
    public int i {get;} = 5;
    public string s {get;} = "Asdf";
    public double d {get;} = 1.5;
}

public class SomeClass
{
    public static SomeType SomeProp { get; } = new SomeType();
}


public static async Task Main()
{
    await Task.WhenAll(
        Task.Run(() => { _ = SomeClass.SomeProp; }),
        Task.Run(() => { _ = SomeClass.SomeProp; })
    );
}

I am concerned about the concurrent read from SomeClass.SomeProp.

It seems to be thread safe since - AFAIK - just reading is always thread safe.

But - again AFAIK - properties are lazily initialized so the first read from SomeProp will actually write new SomeType() to it? So if SomeProp has never been read from and then two threads try to concurrently read from it for the first time then we have a concurrent write and a data race?

Is this the case? If so, then how to make it thread safe (and do I have to protect the read with a full-blown lock?)

gaazkam
  • 151
  • 5
  • https://stackoverflow.com/questions/505515/c-sharp-thread-safety-with-get-set – Radost Jan 13 '22 at 12:16
  • @Radost I'm not sure how can this question you linked to help here? That question mentions *explicit* writes (`MyProperty.Field1 = 2;`) while my question assumes no such writes can take place except for the initialization of the prop. – gaazkam Jan 13 '22 at 12:20
  • Reading `SomeProp` is safe, as it's only written to once. Reading the *fields* of `SomeType` may not be safe, if they are read and written concurrently – canton7 Jan 13 '22 at 12:21
  • @canton7 Fields of `SomeType` are not supposed to be read and written concurrently. Maybe my example is bad, I'm going to change them into props with private setters to stop this from being an issue. Wrt `SomeProp`: When is it written to? Doesn't the first read from `SomeProp` actually write to it? Then what happens if two threads are concurrently trying to make this first read? – gaazkam Jan 13 '22 at 12:24
  • 3
    Static properties may be lazily instantiated -- it gets complex -- but they are guaranteed to be instantiated before they're first accessed, and that instantiation is guaranteed to be thread-safe. This is handled by the runtime. – canton7 Jan 13 '22 at 12:24
  • I'm not sure if static initialisers are thread-safe, but I do know that static constructors are guaranteed to only be executed once per app domain so you may want to move that into a static constructor. – DavidG Jan 13 '22 at 12:26
  • @DavidG Property/field initialisers are just compiler sugar for putting the instantiation in a static ctor - see the `.cctor` [here](https://sharplab.io/#v2:EYLgtghglgdgNAFxFANnAJiA1AHwAIDMABHgExEDKA9mAKYAqAngA60CwAUAN6cC+nnQiXLU6AYRQQAzlM48ORRSWJ4AjADZKNBi1pa6ABQBOVZkS5EA5rQQBuIryIBeIjFoB3fTtYAKAJS2fEA=). Having an explicit static ctor won't make a different to thread-safety (it's safe both with and without it), although it might make a different to when the static ctor is called (see `beforefieldinit`) – canton7 Jan 13 '22 at 12:28
  • @canton7 Well technically there is some difference https://csharpindepth.com/Articles/BeforeFieldInit – DavidG Jan 13 '22 at 12:30
  • 1
    Right, hence my comment about `beforefieldinit` -- but like I said, that only makes a difference to when the static ctor is called. There's still no difference in thread-safety between fields which are initialised in the static ctor, and fields which are set using field/property initialisers (and which the compiler places in that same static ctor) – canton7 Jan 13 '22 at 12:41
  • 1
    Does this answer your question? [Thread safety on readonly static field initialisation](https://stackoverflow.com/questions/12159698/thread-safety-on-readonly-static-field-initialisation) – Charlieface Jan 13 '22 at 12:46

0 Answers0