2 دقیقه میانگین مدت زمان مطالعه است

برای فراخوانی یک متود از نوع Task دو روش زیر را می‌توان استفاده کرد که در این مطلب به توضیح تفاوت آنها می‌پردازیم.
تفاوت اصلی این دو متود در انتشار خطا یا exception propagation است.
در حالت دوم که await دارد، خطا در داخل Task ذخیره می‌شود و تا زمانی که به یکی از موارد await task , task.Wait() , task.Result , task.GetAwaiter().GetResult() نخورد، برگشت داده نمی‌شود.
اما حالت اول درست زمان قبل از بقیه موارد خطا را می‌دهد. در این رابطه می‌توانید مثال آخر را مشاهده کنید.

تفاوت دیگر سربار استفاده از await است.
در صورت استفاده از await متود مورد نظر توسط کامپایلر به state machine تبدیل می‌شود.
اگر در متود شما بعد از await کد دیگری نبود، می‌توانید همان Task را برگردانید و از این سربار جلوگیری کنید.

private Task TestAsync() 
{
    return Task.Delay(1000);
}

private async Task TestAsync() 
{
    await Task.Delay(1000);
}

در صورتی که متود مورد نظر را داخل using یا Scope استفاده کرده باشید، نباید از حالت بدون await استفاده کنید.

BUT never do inside a using, or other scoped construct where the object required by the inner call requires context to successfully complete.

In the following example, the first variation, without the await, returns a database call that escapes from the context of the database connection at the end of the method, so when the call is made, the database connection has been closed. You need the await in the second example to postpone the disposal of the database connection until the GetUserAsync call returns.

class UserRepository
{
    public UserRepository(IDbConnectionFactory dbConnectionFactory)
    {
        _dbConnectionFactory = dbConnectionFactory;
    }

    public Task<UserDetailsResult> GetUserDetails(string userId)
    {
        using (var dbConnection = _dbConnectionFactory.CreateConnection())
        {
            return GetUserAsync(userId, dbConnection);
        }
    }

    public async Task<UserDetailsResult> GetUserDetailsAsync(string userId)
    {
        using (var dbConnection = _dbConnectionFactory.CreateConnection())
        {
            return await GetUserAsync(userId, dbConnection);
        }
    }
}

در مثال زیر هم با توجه به اینکه کدام مورد را فراخوانی کنید، Exception رخ داده در زمان متفاوت نشان داده می‌شود.

private static void DoTestAsync(Func<int, Task> whatTest, int n)
{
    Task task = null;
    try
    {
        // start the task
        task = whatTest(n);

        // do some other stuff, 
        // while the task is pending
        Console.Write("Press enter to continue");
        Console.ReadLine();

        task.Wait();
    }
    catch (Exception ex)
    {
        Console.Write("Error: " + ex.Message);
    }
}

private static async Task OneTestAsync(int n)
{
    await Task.Delay(n);
}

private static Task AnotherTestAsync(int n)
{
    return Task.Delay(n);
}
DoTestAsync(OneTestAsync, -2)

Press enter to continue
Error: One or more errors occurred.await Task.Delay
DoTestAsync(AnotherTestAsync, -2)

Error: The value needs to be either -1 (signifying an infinite timeout), 0 or a positive integer.
Parameter name: millisecondsDelayError: 1st

تفاوت دیگر در زمان استفاده در موارد وابسته به UI است. بطور مثال کد زیر در WPF که مورد گفته شده باعث Dead Lock می‌شود.

public void Form_Load(object sender, EventArgs e)
{
    TestAsync().Wait(); // dead-lock here
    TestAsync2().Wait(); // not dead-lock
}

private static async Task TestAsync()
{
    await Task.Delay(1000);
}

private Task TestAsync2() 
{
    return Task.Delay(1000);
}

sharplab
async-return-types