简体   繁体   中英

Use of Async and await in console Application

I have four methods.

  1. Main: only calls the preform method
  2. Working: displays "please wait for the user"
  3. Taking Time: A program that takes time to execute.
  4. Preform: Calls the taking time and working methods asynchronously.

The following is my code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncObservation
{
    class Program
    {
        static void Main(string[] args)
        {
            preform();
        }
        public static async Task Working()
        {
            Console.WriteLine("Please wait, the program is running");  
        }
        public static async Task Takingtime()
        {
            Console.WriteLine("This Program started");
            Thread.Sleep(1000);
            Console.WriteLine("The Program finished"); 
        }
        public static async void preform()
        {
            Task timer = Takingtime();
            Task wait = Working();
        }
    }
}

In the end: I need to display

This program started. 
Please wait, the program is running 
The program ended.

Use Stephen Cleary's Nito.AsyncEx library (available via Nuget) to provide an asynchronous context for a console application. See here for more details.

Your application can then be written as ...

class Program
{
    static int Main(string[] args)
    {
        try
        {
            Console.WriteLine("The application has started");
            AsyncContext.Run(() => LongRunningTaskAsync(args));
            Console.WriteLine("The application has finished");
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine(ex);
            return -1;
        }
    }

    static async Task LongRunningTaskAsync(string[] args)
    {
        Console.WriteLine("The long running task has started");
        // use Task.Delay() rather than Thread.Sleep() to avoid blocking the application
        await Task.Delay(TimeSpan.FromSeconds(10)).ConfigureAwait(false);
        Console.WriteLine("The long running task has finished");  
    }
}

I see several problems in your program.

  • Although Preform is neither async nor an event handler, it does not return a Task
  • The tasks started in Preform are not awaited for before you finish Preform. Hence you never know when they are finished, nor what the results are (exception?). You could even end your program before they are finished
  • After you start a Task it is not guaranteed when it will run. You can only be sure that statements are already executed if you await for the Task.
  • Using async-await is a method to make sure that your thread looks around to see if it can do useful stuff instead of waiting idly if it has to wait for something. Thread.Sleep is a busy wait. If you want to look around to see if you can do something else use await Task.Delay(TimeSpan.FromSeconds(1)) instead.

In your case, you can't be sure that any Console line has been written until you await the procedure that should write your line. If you start your second Task before awaiting the first, you don't know how far the first task already proceeded, and thus you don't know for sure that the text already has been written to the Console.

C# 7.1 introduced async Task Main() , so you could use that instead of the traditional void Main . It saves you from catching and interpreting the AggregateException that is thrown by the Task you start to make your process async.

If you don't want to use the async Main , you can of course use Task.Run to call an async function:

static void Main(string[] args)
{
    try
    {
        var preformTask = Task.Run( () => Preform() );

        DoSomethingElse();    // if needed
        preformTask.Wait();   // wait for preformTask to finish

        Console.WriteLine("Task completed; press any key to finish");
        Console.ReadKey();
    }
    catch (Exception exc) // inclusive ggregateException if one of your Task fails
    {
        ProcessException(exc)
    }
}

static async Task preform()
{
    // To be certain that the Console Line has been written: await
    await Takingtime();

    // if here, you are certain that the Line has been written,
    // or course you have lost parallel processing
    await Working();
}

For completeness: the other functions

public static async Task Working()
{
    Console.WriteLine("Please wait, the program is running");

    // either return a completed Task, or await for it (there is a difference!
    await Task.CompletedTask;
    // or:
    return Task.CompletedTask; // do not declare async in this case
}

public static async Task Takingtime()
{
    Console.WriteLine("This Program started");

    //Use Task.Delay instead of Sleep
    await Task.Delay(TimeSpan.FromSeconds(1);   // improved readability
    Console.WriteLine("The Program finished");
}

Because of the awaits in Preform you are certain that the text has been written. However, you've lost some parallellism.

If you really want those procedures to execute at the same time, you can't be certain about when text will be written. If that is important, then split the Parts that should be run first (write Console) from the parts that should run in parallel (Task.Delay)

static async Task preform()
{
    // Do the things that should be done before parallel tasks are run
    await DoThisFirst();

    // start the Tasks that can work parallel: not sure what statements are executed first
    var taskA = DoTaskA();
    var taskB = DoTaskB();

    // if here, you are free to do something else
    // can't be sure about the status of taskA nor taskB
    DoSomethingElse();

    // if you want to do something after you know that the tasks have completed:
    // await the tasks here:
    await Task.When (new Task[] {taskA, taskB});

    // if here, you are certain that all parallel tasks have completed successfully
    // if desired fetch the return values of the Tasks:
    var returnValueA = taskA.Result;
    var returnValueB = taskB.Result;

    // do all async things of which you needed to be certain that both tasks finished
    // for example:
    await ProcessResults(returnValueA, returnValueB);
}

In a console app it is OK to use a .Wait() call in an void Main method.

In some contexts where there is synchronisation necessary .Wait() can cause deadlocks (ASP.NET has a request to synchronise on or XAML/WinForms which have a UI thread), but there is nothing to synchronise on here.

static void Main() 
{
     preform.Wait()
}

This will wait for the async work to complete synchronously. You need the method to run synchronously so that it does not return early. If you use async void and await the method will return immediately and exit the method, await does not work in a Main method.

I would also suggest using await Task.Delay(1000); rather than Thread.Sleep(1000); as this is the canonical async way to idle.

For your code example:

class Program
{
    static void Main(string[] args)
    {
        preform().Wait();

        Console.ReadLine();
    }

    public static async Task Working()
    {
        Console.WriteLine("Please wait, the program is running");
    }

    public static async Task Takingtime()
    {
        Console.WriteLine("This Program started");
        await Task.Delay(1000);
        Console.WriteLine("The Program finished");
    }

    public static Task preform()
    {
        return Task.WhenAll(
            Takingtime(),
            Working());
    }
}

Use preform().Wait() in the constructor. So the call is awaited. Then use await Takingtime() and await Working() in the preform method. And you have to change the return type to Task .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM