【问题标题】:Can I make sure a static constructor has finished before I create an instance?我可以在创建实例之前确保静态构造函数已经完成吗?
【发布时间】:2019-06-12 19:33:00
【问题描述】:

我的类有一个在构造函数中使用的静态字段初始化器:

class Foo {
  private static List<string> list = new List<string>()
  private static object listLock = new Object();

  public Foo(string s) {
    lock(listLock)
      list.Add(s);
  }
}

我的问题是,它偶尔会发生,在静态初始化程序完成之前在构造函数中访问列表,导致访问list 时出现NullReferenceException。根据this question,只保证静态初始化在实例创建之前启动,而不是完成。

是否有某种方法可以确保仅在静态构造函数完成后才调用构造函数(除了像while(list == null){} 这样的丑陋黑客)?

【问题讨论】:

  • @DanielA.White 那是......不太正确 :) 啊,好 ol' before-field-init
  • 如果您只使用显式静态构造函数,这不应该有效吗?
  • 静态初始化器和实例构造器都在同一个线程中运行。因此,假设静态初始化器不创建类的实例,静态初始化器将在任何实例构造函数开始之前完成。
  • @TheodorZoulias:这对于初始化程序是不正确的,如我的示例所示。但如果我按照this answer 中的建议创建一个显式静态构造函数,它就会起作用

标签: c# .net constructor .net-core static


【解决方案1】:

Here's a good link on the nuances of before-field-init,但确实:添加显式静态构造函数应该强制运行时在此处处理。请注意,while (list == null) {} 将不起作用,因为它会迫使运行时使用 - 基本上,您应该永远无法观察到运行时告诉您的谎言。

在所示示例中,没关系。你永远不会看到null 对应于listLocklist。如果你真的要求它们在构造函数之前运行:

private static List<string> list;
private static object listLock;
static Foo() {
    list = new List<string>();
    listLock = new object();
}

但请注意,这并不是真正必要的,并且可能对您的代码产生负面影响,尤其是 .NET Core 3 中的新 JIT 可以用额外的巫术来处理 static readonly 字段如果他们被整齐地初始化(我知道如果它们是没有显式静态构造函数的内联字段初始化器,它可以做到这一点;我不知道它是否可以做到这一点如果它们是由显式静态构造函数分配的)。

【讨论】:

  • 嗯,我有时会看到列表的空值,这就是我发现有问题的原因。
  • @mat 将其标记为readonly;我强烈怀疑某些代码正在改变它;除非您破坏了运行时(公平地说可能发生,但这种情况非常罕见):您不会因为初始化竞赛而将其视为 null
【解决方案2】:

我无法重现该问题。下面的程序并行创建了 5 个Foo 实例,每个Foo 实例的构造函数使用类SlowObject 的静态实例作为锁,实例化需要500 毫秒。尽管如此,SlowObject 总是在任何Foo 构造函数启动之前创建。我使用各种版本的 .NET Framework、C# 版本 4、5、6 和 7 测试了这个程序,同时带有 DebugRelease 配置,并带有和不带有调试器。输出始终相同。注释掉静态构造函数没有区别。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;

class Program
{
    static Program()
    {
        Program.ConsolePrint("Program Static Constructor");
    }

    static void Main(string[] args)
    {
        ConsolePrint("Starting Tasks");
        var tasks = Enumerable.Range(1, 5).Select(n => Task.Run(() =>
        {
            new Foo(n.ToString());
        })).ToArray();
        ConsolePrint("Waiting Tasks");
        Task.WaitAll(tasks);
        ConsolePrint("Tasks Finished");
        //Console.WriteLine("Foo.list: " + String.Join(", ", Foo.GetItems()));
    }

    public static void ConsolePrint(string line)
    {
        Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff") + " ["
            + Thread.CurrentThread.ManagedThreadId.ToString() + "] > " + line);
    }
}

public class Foo
{
    static Foo()
    {
        Program.ConsolePrint("Foo Static Constructor");
    }

    private static List<string> list = new List<string>();
    private static object listLock = new SlowObject();

    public Foo(string s)
    {
        Program.ConsolePrint("Creating Foo " + s);
        lock (listLock)
        {
            list.Add(s);
        }
    }
}

public class SlowObject
{
    static SlowObject()
    {
        Program.ConsolePrint("SlowObject Static Constructor");
    }

    public SlowObject()
    {
        Program.ConsolePrint("SlowObject Instance Constructor Started");
        Thread.Sleep(500);
        Program.ConsolePrint("SlowObject Instance Constructor Finished");
    }
}

输出:

13:40:24.091 [1] > Program Static Constructor
13:40:24.112 [1] > Starting Tasks
13:40:24.131 [1] > Waiting Tasks
13:40:24.132 [3] > SlowObject Static Constructor
13:40:24.133 [3] > SlowObject Instance Constructor Started
13:40:24.635 [3] > SlowObject Instance Constructor Finished
13:40:24.635 [3] > Foo Static Constructor
13:40:24.637 [3] > Creating Foo 1
13:40:24.637 [4] > Creating Foo 2
13:40:24.643 [3] > Creating Foo 5
13:40:24.639 [5] > Creating Foo 3
13:40:24.641 [6] > Creating Foo 4
13:40:24.647 [1] > Tasks Finished
Press any key to continue . . .

【讨论】:

  • 不是确定性问题,只是偶尔发生
  • 问题的不确定性表明程序中的某处存在竞争条件。在这种情况下,应该可以通过添加人为延迟来夸大问题。我的测试表明执行顺序定义明确,静态初始化程序必须在任何实例构造函数启动之前完成。老实说,我倾向于相信你的情况还有其他问题。
猜你喜欢
  • 1970-01-01
  • 2011-05-08
  • 2018-07-18
  • 2011-04-19
  • 2012-07-22
  • 2019-09-22
  • 2017-06-23
  • 2011-02-24
  • 1970-01-01
相关资源
最近更新 更多