async/await whenall does return immediately

2019-02-26 12:50发布

问题:

I have this "simple" test code... (Don't bother the strange use of the Class methods...)

I am trying to grasp the Task<> intricacies... I think I have a little understanding of Task<>.Start()/Task<>.Result pattern (maybe as it resembles more the 'old' Thread.Start()?) but as soon as it seems to me to grasp something (and so I throw in the await keyword)... then all entangles again :-(

Why my code returns immediately after the first task completes? Why it doesn't wait on the Task.WhenAll()?

static BigInteger Factorial(BigInteger factor)
{
    BigInteger factorial = 1;
    for (BigInteger i = 1; i <= factor; i++)
    {
        factorial *= i;
    }
    return factorial;
}

private class ChancesToWin
{
    private int _n, _r;

    public ChancesToWin(int n, int r)
    {
        _n = n;
        _r = r;
    }

    private Task<BigInteger> CalculateFactAsync(int value)
    {
        return Task.Factory.StartNew<BigInteger>(() => Factorial(value));
    }

    public async Task<BigInteger> getFactN()
    {
        BigInteger result = await CalculateFactAsync(_n);
        return result;
    }

    public async Task<BigInteger> getFactN_R()
    {
        BigInteger result = await CalculateFactAsync(_n - _r);
        return result;
    }

    public async Task<BigInteger> getFactR()
    {
        BigInteger result = await CalculateFactAsync(_r);
        return result;
    }

}

private async static void TaskBasedChancesToWin_UseClass()
{
    int n = 69000;
    int r = 600;

    List<Task<BigInteger>> tasks = new List<Task<BigInteger>>();

    ChancesToWin ctw = new ChancesToWin(n, r);

    tasks.Add(ctw.getFactN());
    tasks.Add(ctw.getFactN_R());
    tasks.Add(ctw.getFactR());

    // The getFactR() returns first of the other two tasks... and the code exit!
    BigInteger[] results = await Task.WhenAll(tasks);

    // I don't get here !!!!
    BigInteger chances = results[0] / results[1] * results[2];

    //Debug.WriteLine(chances);
}

static void Main(string[] args)
{
    TaskBasedChancesToWin_UseClass();
}

回答1:

Async methods run synchronously until the first await when they return control to the calling method, usually returning a task representing the rest of the asynchronous operation. TaskBasedChancesToWin_UseClass doesn't return a task so the caller can't wait for it to complete. That's why you shouldn't use async void outside of event handlers.

Since Main doesn't wait for the operation your application ends before the operation had a chance to complete.

You would usually wait with await but since you Main can't be an async method you can block synchronously with Wait on the task returned from TaskBasedChancesToWin_UseClass:

async static Task TaskBasedChancesToWin_UseClass()
{
    // ...
}

static void Main()
{
    TaskBasedChancesToWin_UseClass().Wait();
}