Question

This is an "academic" question as opposed to one affecting anything I am currently working on in an official capacity, but I figure somebody here can explain this to me.

I'm playing around with CancellationToken in LinqPAD (I mention this detail so people unfamiliar with it know that Dump() is basically short-hand for Console.WriteLine()) and I was trying out a scenario where I have the main thread sleep while my task completes. This was done after running several variations of cancelling the task before it could complete to play with the IsCanceled property and verify that the Result property is not accessible when cancelled. Here's what my dummy LinqPAD program looks like:

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();
    }
}

This behaves--mostly--how I would expect: the output was "Completed" followed by "45". What I did not expect was that the "45" was not output until the Thread.Sleep() call finished, but "Completed" was output instantly.

So my question is: how can the IsCompleted flag of my task be evaluated immediately, but the Result property not be evaluated until after the calling thread returns from a Thread.Sleep() call? Additionally, is there any way I could restructure this to have either both be output immediately or both wait for the end of the Sleep() call?

I know this example is contrived, but I can't wrap my head around this contrived behavior.
EDIT
Additional question: doesn't this observed behavior imply that I could get in a state where the task's Result property changes between when IsCompleted is evaluated and when I am able to evaluate Result?
EDIT 2
After further investigation, I was able to answer one of my own questions: I can force the "Completed" output to wait until after the Thread.Sleep() completes by changing the behavior of Test() to return an IList<> that is built-up in the method instead of yield returning an IEnumerable. I understand how IEnumerable works generally, but the behavior I'm seeing here by introducing tasks is what is throwing me off.

Was it helpful?

Solution

That's because of the way 'yield return' and IEnumerable<> works.

So when you call Test() it doesn't actually running the loop, it just returns an IEnumerable<>.

To prove you can run this code -

    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);
    }

So only after this line of code- t.Result.Sum (r => r).Dump(); You start looping and waiting on the Task.Delay(500).Wait(); When finished will sum up the numbers and write it to the console.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top