在C#控制台应用程序中使用'async'关键字

211

我有这段简单的代码:

public static async Task<int> SumTwoOperationsAsync()
{
    var firstTask = GetOperationOneAsync();
    var secondTask = GetOperationTwoAsync();
    return await firstTask + await secondTask;
}


private async Task<int> GetOperationOneAsync()
{
    await Task.Delay(500); // Just to simulate an operation taking time
    return 10;
}

private async Task<int> GetOperationTwoAsync()
{
    await Task.Delay(100); // Just to simulate an operation taking time
    return 5;
}

很好。这可以编译。

但是假设我有一个控制台应用程序,并且我想运行上面的代码(调用SumTwoOperationsAsync())。

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

但是我读到(当使用sync时)必须同步全部进行

这是否意味着我的Main函数应该标记为async

好吧,不行,因为会有编译错误:

入口点不能标记为'async'修饰符

如果我理解异步的东西,线程将进入Main函数→SumTwoOperationsAsync→将调用两个函数并退出。但在SumTwoOperationsAsync之前,线程仍然会阻塞。

我错过了什么吗?


6
这里有一个C# 7.1的正确解决方案 - nawfal
3
@nawful似乎错过了一个关键的链接,所以在这里提供它...https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-7-1#async-main - 在您的Visual Studio 2017项目中,转到项目属性->生成->高级,然后将语言版本更改为7.1(或更高)。 - alv
4个回答

357

在大多数项目类型中,您的async "上升"和"下降"将以async void事件处理程序结束或返回Task给您的框架。

然而,控制台应用程序不支持此操作。

您可以在返回的任务上执行Wait

static void Main()
{
  MainAsync().Wait();
  // or, if you want to avoid exceptions being wrapped into AggregateException:
  //  MainAsync().GetAwaiter().GetResult();
}

static async Task MainAsync()
{
  ...
}

或者你可以使用自己编写的上下文

static void Main()
{
  AsyncContext.Run(() => MainAsync());
}

static async Task MainAsync()
{
  ...
}

async控制台应用的更多信息请查看我的博客


1
如果它不是控制台应用程序,那么我的观察(问题的最后一行)是正确的吗?线程在SumTwoOperationsAsync停止了...对吗?(假设主调用方法没有标记为异步) - Royi Namir
2
我在我的博客上有一篇介绍,解释了如何通过“上下文”使用async来工作线程。 - Stephen Cleary
1
重新阅读您的回答 - 您是在说在控制台应用程序中使用异步是毫无意义的吗? - Royi Namir
1
@RoyiNamir:不是很常见,除非你正在使用异步 API。控制台(和其他桌面)应用程序通常不太关心浪费线程。 - Stephen Cleary
1
@user2173353:不,它保证会被catch捕获。如果您没有看到这种情况,我建议您提出自己的问题,并提供一个最小的代码示例。 - Stephen Cleary
显示剩余7条评论

103
这是最简单的方法:
static void Main(string[] args)
{
    Task t = MainAsync(args);
    t.Wait();
}

static async Task MainAsync(string[] args)
{
    await ...
}

4
作为一种快速而又具有范围限制的解决方案:

Task.Result

无论是使用Task.Result还是Task.Wait,都不能在与I/O一起使用时提高可扩展性,因为它们会导致调用线程阻塞等待I/O结束。

当您在未完成的Task上调用.Result时,执行该方法的线程必须坐等任务完成,这会阻塞线程在此期间无法执行任何其他有用的工作。这抵消了任务异步特性的好处。

notasync


-1

我的解决方案。JSONServer是我编写的一个类,用于在控制台窗口中运行HttpListener服务器。

class Program
{
    public static JSONServer srv = null;

    static void Main(string[] args)
    {
        Console.WriteLine("NLPS Core Server");

        srv = new JSONServer(100);
        srv.Start();

        InputLoopProcessor();

        while(srv.IsRunning)
        {
            Thread.Sleep(250);
        }

    }

    private static async Task InputLoopProcessor()
    {
        string line = "";

        Console.WriteLine("Core NLPS Server: Started on port 8080. " + DateTime.Now);

        while(line != "quit")
        {
            Console.Write(": ");
            line = Console.ReadLine().ToLower();
            Console.WriteLine(line);

            if(line == "?" || line == "help")
            {
                Console.WriteLine("Core NLPS Server Help");
                Console.WriteLine("    ? or help: Show this help.");
                Console.WriteLine("    quit: Stop the server.");
            }
        }
        srv.Stop();
        Console.WriteLine("Core Processor done at " + DateTime.Now);

    }
}

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