任务结果评估的时间安排

Sve*_*sen 1 .net multithreading task-parallel-library c#-5.0

这是一个"学术"问题,而不是影响我目前正在以官方身份工作的任何问题,但我想这里有人可以向我解释这个问题.

我与玩弄CancellationTokenLinqPAD(我提到这个细节让人们不熟悉它知道,Dump()基本上是短手Console.WriteLine()),我是想了这样一个场景,我有主线程睡眠,而我的任务完成.这是在运行几个取消任务的变体之后完成的,然后才能完成与IsCanceled属性一起玩并验证Result在取消时无法访问该属性.这是我的虚拟LinqPAD程序的样子:

void Main()
{
    var cts = new CancellationTokenSource();
    var task = Task.Factory.StartNew(() => Test())
                            .ContinueWith(t => t.Result, cts.Token)
                            .ContinueWith(t => 
                            { 
                                if (t.IsCanceled)
                                {
                                    "Cancelled".Dump();
                                }
                                else if (t.IsCompleted)
                                {
                                    "Completed".Dump();
                                    t.Result.Sum (r => r).Dump();
                                }
                            }); 
    //sleep long enough to make sure the Task completes
    Thread.Sleep(6000);
}

private IEnumerable<int> Test()
{
    foreach(var i in Enumerable.Range(0, 10))
    {
        yield return i;
        Task.Delay(500).Wait();
    }
}
Run Code Online (Sandbox Code Playgroud)

这种行为 - 大多数 - 我的期望:输出是"完成",然后是"45".我没想到的是在Thread.Sleep()调用完成之前没有输出"45",但是立即输出"Completed".

所以我的问题是:如何IsCompleted立即评估我的任务标志,但是Result直到调用线程从调用返回后才能评估属性Thread.Sleep()?另外,有没有什么办法可以重构这个,既可以立即输出,也可以等待Sleep()呼叫结束?

我知道这个例子是人为的,但我不能围绕这种做作的行为.
编辑
附加问题:这种观察到的行为是否意味着我可以进入任务的Result属性在IsCompleted评估时和我能够评估之间发生变化的状态Result
编辑2
经过进一步的调查,我能够回答我自己的一个问题:我可以强制"完成"输出等到Thread.Sleep()完成后通过改变行为Test()来返回在方法中建立的IList <>而不是yield returnIEnumerable.我理解IEnumerable一般是如何工作的,但我通过介绍任务看到的行为就是让我失望的原因.

Max*_*xim 5

这是因为'yield return'和IEnumerable <>的工作方式.

因此,当您调用Test()时,它实际上并不运行循环,它只返回一个IEnumerable <>.

为了证明你可以运行这段代码 -

    var stopwatch = Stopwatch.StartNew();
    var results = Test();


    Console.WriteLine("Time after running Test {0}", stopwatch.ElapsedMilliseconds);

    foreach (var result in results)
    {
        Console.WriteLine("Looping {0}", stopwatch.ElapsedMilliseconds);
    }
Run Code Online (Sandbox Code Playgroud)

所以只有在这行代码之后 - t.Result.Sum(r => r).Dump(); 你开始循环并等待Task.Delay(500).Wait(); 完成后将总结数字并将其写入控制台.