0

Below is the Main method code,

static void Main(string[] args)
    {
        var taskList = new List<Task<bool>>();

        try
        {
            for (var i = 0; i < 10; i++)
            {
                taskList.Add(Processor.Process(i));
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
    }

And the Process method,

public static class Processor
{
    public static async Task<bool> Process(int num)
    {
        if (num % 2 == 0)
        {
            Console.Write(num);
        }
        else
        {
            throw new Exception("Hello Exception");
        }

        await Task.Delay(1);
        return true;
    }
}

The output of the program is 02468. I have 2 questions,

  1. Why the exception not delegating to Main method in case of odd number according to the code?

  2. With above code my thought that task is just added and it will not go for processing until I use something like var responses = await Task.WhenAll(taskList);, but I noticed it's processing, why is this nature, can we hold processing and instructing at once?

Thanks.

user584018
  • 10,186
  • 15
  • 74
  • 160
  • 1
    FYI: Until `await Task.Delay(1);` your task is running synchronously on the same thread as your `for` loop. Slight simplification, but: when it hits the `await` it will suspend the processing and return to `Main` to evaluate `taskList.Add`. I believe at that stage exceptions are still wrapped up in a `Task` though. I could be mistaken. – ProgrammingLlama Jul 01 '22 at 03:08
  • Thanks @DiplomacyNotWar. so this means still the processing is not complete and it's in evaluate state `taskList.Add`, then why I get result for 0,2,4, etc – user584018 Jul 01 '22 at 03:14
  • 1
    Because that's executed on the same thread as your `for` loop _before_ the task is returned. – ProgrammingLlama Jul 01 '22 at 03:36
  • 2
    Related to your first question: [Why do unawaited async methods not throw exceptions?](https://stackoverflow.com/questions/24441790/why-do-unawaited-async-methods-not-throw-exceptions) Related to your second question: [Enforce an async method to be called once](https://stackoverflow.com/questions/28340177/enforce-an-async-method-to-be-called-once) – Theodor Zoulias Jul 01 '22 at 04:23

1 Answers1

1

Have a look at compiler generated code, you will find:

  1. In the MoveNext method, exception is catched and saved in <>t__builder.
    You may also notice await Task.Delay(1) is converted to awaiter = Task.Delay(1).GetAwaiter(), and awaiter.GetResult() will automatically check Task.Exception for you, that's why awaiting a task will throw exception.

  2. When Processor.Process is invoked, it will call <>t__builder.Start to start the task, so you don't need something like await.
    To hold processing a task you can use Task's constructors to create one and use Start method to run it.

public static class Processor
{
    [StructLayout(LayoutKind.Auto)]
    [CompilerGenerated]
    private struct <Process>d__0 : IAsyncStateMachine
    {
        public int <>1__state;

        public AsyncTaskMethodBuilder<bool> <>t__builder;

        public int num;

        private TaskAwaiter <>u__1;

        private void MoveNext()
        {
            int num = <>1__state;
            bool result;
            try
            {
                TaskAwaiter awaiter;
                if (num != 0)
                {
                    if (this.num % 2 != 0)
                    {
                        throw new Exception("Hello Exception");
                    }
                    Console.WriteLine(this.num);
                    awaiter = Task.Delay(1).GetAwaiter();
                    if (!awaiter.IsCompleted)
                    {
                        num = (<>1__state = 0);
                        <>u__1 = awaiter;
                        <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
                        return;
                    }
                }
                else
                {
                    awaiter = <>u__1;
                    <>u__1 = default(TaskAwaiter);
                    num = (<>1__state = -1);
                }
                awaiter.GetResult();
                result = true;
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <>t__builder.SetException(exception);
                return;
            }
            <>1__state = -2;
            <>t__builder.SetResult(result);
        }

        void IAsyncStateMachine.MoveNext()
        {
            //ILSpy generated this explicit interface implementation from .override directive in MoveNext
            this.MoveNext();
        }

        [DebuggerHidden]
        private void SetStateMachine(IAsyncStateMachine stateMachine)
        {
            <>t__builder.SetStateMachine(stateMachine);
        }

        void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
        {
            //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
            this.SetStateMachine(stateMachine);
        }
    }

    [AsyncStateMachine(typeof(<Process>d__0))]
    public static Task<bool> Process(int num)
    {
        <Process>d__0 stateMachine = default(<Process>d__0);
        stateMachine.<>t__builder = AsyncTaskMethodBuilder<bool>.Create();
        stateMachine.num = num;
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }
}
shingo
  • 18,436
  • 5
  • 23
  • 42