在控制台应用中使用异步和等待功能

5

我有四种方法。

  1. Main:仅调用preform方法
  2. Working:显示“请等待用户”
  3. Taking Time:一个需要花费时间来执行的程序
  4. Preform:异步地调用taking time和working方法。

以下是我的代码:

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

最后:我需要展示。
This program started. 
Please wait, the program is running 
The program ended.

6
这里的问题是什么?避免使用“async void”。 - Rahul
4个回答

7

我看到您的程序存在几个问题。

  • Preform既不是异步方法也不是事件处理程序,但它没有返回Task
  • 在您完成Preform之前没有等待启动的任务完成。因此,您永远不知道它们何时完成,也不知道结果(异常?)。甚至可能在它们完成之前就结束了程序。
  • 启动任务后并不能保证什么时候它们会运行。只有在等待任务时才能确保语句已经执行。
  • 使用异步-等待是一种方法,可以确保线程查看是否可以执行有用的任务而不是无所事事地等待。 Thread.Sleep是一种繁忙等待的方法。如果要查看是否可以执行其他任务,请改用await Task.Delay(TimeSpan.FromSeconds(1))

在您的情况下,只有在等待应该写入您的文本的过程后,才可以确定任何控制台行是否已被写入。如果在等待第一个任务之前启动第二个任务,则不知道第一个任务已经进行到何种程度,因此无法确定文本已经写入控制台。

C# 7.1引入了异步Task Main(),因此您可以使用它来替代传统的void Main。这样可以避免捕获和解释通过启动异步任务抛出的AggregateException异常。

如果您不想使用async Main,当然也可以使用Task.Run来调用异步函数:

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

为了完整性:其他函数
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");
}

由于在Preform中使用了awaits,因此您可以确定文本已被写入。但是,您失去了一些并行性。
如果您真的希望这些过程同时执行,那么您无法确定何时会写入文本。如果这很重要,那么请将应首先运行的部分(写入控制台)与应该并行运行的部分(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);
}

2
在控制台应用程序中,可以在void Main方法中使用.Wait()调用。
在某些需要同步的上下文中,.Wait()可能会导致死锁(例如ASP.NET需要同步请求或具有UI线程的XAML / WinForms),但是在这里没有需要同步的内容。
static void Main() 
{
     preform.Wait()
}

这将会等待异步工作同步完成。您需要让方法同步运行,以便它不会提前返回。如果您使用async voidawait,该方法将立即返回并退出方法,awaitMain方法中无法使用。
我还建议使用await Task.Delay(1000);而不是Thread.Sleep(1000);,因为这是规范的async空闲方式。
对于您的代码示例:
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());
    }
}

我已经根据我的建议更新了我的答案,并提供了一个完整的可工作示例 :) - tigerswithguitars
你可以尝试这个答案,它关于async/await和异步控制台程序。 - Rogerson Nazário
.GetAwaiter().GetResult() 会解开异常,如果有的话,而 .Wait() 则不会。 - Paulo Morgado
@PauloMorgado,不完全正确。它会抛出它遇到的第一个异常,可能会隐藏在其他仍在等待检查的任务中抛出的异常。Wait()将抛出一个包含传递给WhenAll()的所有任务期间抛出的异常的AggregateException。使用上述代码,向两个被调用的任务添加异常,有一点不同。 - tigerswithguitars
这就是 async-await 的行为,而且问题被标记为 async-await。同时也是 async Main 的行为。 - Paulo Morgado

2
使用Stephen Cleary的Nito.AsyncEx库(可通过Nuget获取)为控制台应用程序提供异步上下文。有关更多详细信息,请参见此处
然后,您可以编写以下应用程序...
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");  
    }
}

1
在构造函数中使用preform().Wait(),以便调用被等待。然后在preform方法中使用await Takingtime()await Working()。还需要将返回类型更改为Task

async void 表示它是不可等待的。这就是为什么不应该使用 async void - Panagiotis Kanavos
哦,是的,你必须使用Task作为异步方法的返回类型。 - juliushuck
我已经将其添加到答案中。 - juliushuck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接