为什么使用ExecutionAndPublication的Lazy <t>与产生IEnumerables的行为不同?

时间:2017-07-26 17:42:57

标签: c# ienumerable lazy-initialization

我的代码中出现了一个问题,即Lazy初始化程序的调用频率超出了我的预期。从文档中,我预计使用LazyThreadSafetyMode.ExecutionAndPublication将确保我的初始化函数只被调用一次,例如,如果在定义后访问numbers.Value:

numbers = new Lazy<IEnumerable<int>>(
        () => GetNumbers(),
        LazyThreadSafetyMode.ExecutionAndPublication
    );

但是,我发现如果初始化函数产生结果,初始化函数会被多次调用。我认为这必须延迟执行产量,但我只有模糊的理由。

问题:

在下面的代码中,为什么各个初始化函数执行的次数不同?

void Main()
{
    var foo         = new foo();
    var tasks       = new List<Task>();

    for (int i = 0; i < 10; ++i) tasks.Add(Task.Run(() => {foreach (var number in foo.Numbers) Debug.WriteLine(number);})); 
    Task.WaitAll(tasks.ToArray());
    tasks.Clear();
    for (int i = 0; i < 10; ++i) tasks.Add(Task.Run(() => {foreach (var letter in foo.Letters) Debug.WriteLine(letter);})); 
    Task.WaitAll(tasks.ToArray());
}

public class foo
{
    public IEnumerable<int> Numbers => numbers.Value;
    public IEnumerable<char> Letters => letters.Value;
    readonly Lazy<IEnumerable<int>> numbers;
    readonly Lazy<IEnumerable<char>> letters;

    public foo()
    {
        numbers = new Lazy<IEnumerable<int>>(
            () => GetNumbers(),
            LazyThreadSafetyMode.ExecutionAndPublication
        );
        letters = new Lazy<IEnumerable<char>>(
            () => GetLetters().ToList(), //ToList enumerates all yielded letters, creating the expected call once behavior
            LazyThreadSafetyMode.ExecutionAndPublication
        );
    }

    protected IEnumerable<char> GetLetters()
    {
        Debug.WriteLine($"{nameof(GetLetters)} Called");
        yield return 'a';
        yield return 'b';
        yield return 'c';
        yield break;
    }
    protected IEnumerable<int> GetNumbers()
    {
        Debug.WriteLine($"{nameof(GetNumbers)} Called");
        yield return 1;
        yield return 2;
        yield return 3;
        yield break;
    }
}

1 个答案:

答案 0 :(得分:5)

  

我的代码中出现了一个问题,即Lazy初始化程序的调用频率超出了我的预期。

不,懒惰的初始化程序被调用一次。懒惰的初始化程序是

() => GetNumbers()

,这恰好被称为。

GetNumbers返回IEnumerable<int> - 一个整数序列。

当您foreach该序列时,它会调用GetEnumerator来获取枚举器,然后在枚举器对象上调用MoveNext,直到MoveNext返回false

您已经说过要将序列枚举为:

the first time MoveNext is called, do a writeline and produce 1
the second time MoveNext is called, produce 2
the third time MoveNext is called, produce 3
the fourth time MoveNext is called, produce 4
Every subsequent time MoveNext is called, return false

所以每当你枚举序列时,就会发生这种情况。

你能解释一下你预期会发生什么吗?我有兴趣了解为什么人们对计算机程序有错误的信念。

我也不清楚你为什么要使用Lazy。在需要之前,您通常会使用Lazy来避免昂贵的工作,但序列已经延迟工作,直到枚举它们为止。