这是C#编译器所做的优化吗?

4

我通过lambda表达式创建了一批匿名函数,我想使用TaskId来将它们区分开。以下是代码:

int count = 3;
int i;
for (int j = 0; j < 10; j++)
{
    i = 0;
    Func<bool, Task<int>> func = async (b) =>
    {
        return j;
    };
    while (i++ < count)
    {
        var task = func(true);
        Console.WriteLine(String.Format("Task Result:{0} TaskId:{1}",
            task.Result, task.Id));
    }
}

以下是输出结果

Task Result:0 TaskId:1
Task Result:0 TaskId:1
Task Result:0 TaskId:1
Task Result:1 TaskId:2
Task Result:1 TaskId:2
Task Result:1 TaskId:2
Task Result:2 TaskId:3
Task Result:2 TaskId:3
Task Result:2 TaskId:3
Task Result:3 TaskId:4
Task Result:3 TaskId:4
Task Result:3 TaskId:4
Task Result:4 TaskId:5
Task Result:4 TaskId:5
Task Result:4 TaskId:5
Task Result:5 TaskId:6
Task Result:5 TaskId:6
Task Result:5 TaskId:6
Task Result:6 TaskId:7
Task Result:6 TaskId:7
Task Result:6 TaskId:7
Task Result:7 TaskId:8
Task Result:7 TaskId:8
Task Result:7 TaskId:8
Task Result:8 TaskId:9
Task Result:8 TaskId:9
Task Result:8 TaskId:9
Task Result:9 TaskId:10
Task Result:9 TaskId:11
Task Result:9 TaskId:12

如您所见,由于结果大于8,TaskId已更改。我想知道这种现象的原因。谢谢你的帮助 :)


据我所知,这很有趣,它正在重用任务实例(当将“task”与先前的值进行比较时,“object.ReferenceEquals”为真),但不清楚原因。 - Mike Zboray
@mikez 是的。但我不知道返回值为什么会影响重用。 - Simon Wu
任务被缓存到某个地方了。连续在一个方法中运行该代码两次。对于<=8,相同的任务ID将重复出现,但是超过8以上的则是新的。 - Mike Zboray
2个回答

2
并不是编译器本身在进行缓存,而是.NET框架在进行缓存。查看 AsyncMethodBuilder 的代码,该代码用于管理异步方法生成的状态机,编译器使用它来实现异步方法。看起来 SetResultGetTaskForResult 中进行了一些缓存操作。这里有一些针对基本值类型的特定代码,包括以下内容:
// For Int32, we cache a range of common values, e.g. [-1,4).
else if (typeof(TResult) == typeof(Int32))
{
    // Compare to constants to avoid static field access if outside of cached range.
    // We compare to the upper bound first, as we're more likely to cache miss on the upper side than on the 
    // lower side, due to positive values being more common than negative as return values.
    Int32 value = (Int32)(object)result;
    if (value < AsyncTaskCache.EXCLUSIVE_INT32_MAX &&
        value >= AsyncTaskCache.INCLUSIVE_INT32_MIN)
    {
        Task<Int32> task = AsyncTaskCache.Int32Tasks[value - AsyncTaskCache.INCLUSIVE_INT32_MIN];
        return JitHelpers.UnsafeCast<Task<TResult>>(task); // UnsafeCast avoids a type check we know will succeed
    }
}

我们看到:

/// <summary>The minimum value, inclusive, for which we want a cached task.</summary>
internal const Int32 INCLUSIVE_INT32_MIN = -1;
/// <summary>The maximum value, exclusive, for which we want a cached task.</summary>
internal const Int32 EXCLUSIVE_INT32_MAX = 9;

这就是为什么8是神奇的分界线。值得注意的是,-1也应该被缓存,并且根据我的测试,它确实被缓存了。

非常好的回答。非常感谢! - Simon Wu

0

Lambda表达式存储变量而不是值。您的表达式是异步的,因此我认为线程会混淆结果。尝试不使用异步操作,它将显示正确的结果。


这段代码是一个样例。我真的想知道为什么会出现混乱。 - Simon Wu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接