无法在控制台应用的“Main”方法上指定“async”修饰符

Can't specify the 'async' modifier on the 'Main' method of a console app

提问人:danielovich 提问时间:2/9/2012 最后编辑:Liamdanielovich 更新时间:10/30/2023 访问量:309776

问:

我是使用修饰符进行异步编程的新手。我正在尝试弄清楚如何确保我的控制台应用程序方法实际上异步运行。asyncMain

class Program
{
    static void Main(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        var list = bs.GetList();
    }
}

public class Bootstrapper {

    public async Task<List<TvChannel>> GetList()
    {
        GetPrograms pro = new GetPrograms();

        return await pro.DownloadTvChannels();
    }
}

我知道这不是从“顶部”异步运行的。由于无法在方法上指定修饰符,因此如何在异步运行代码?asyncMainmain

C# .NET 异步 控制台应用程序

评论

42赞 Vasily Sliounaiev 9/28/2017
在 C#7.1 中不再出现这种情况。主要方法可以是异步的
4赞 styfle 11/4/2017
下面是 C#7.1 博客文章公告。请参阅标题为“异步主”的部分。

答:

3赞 Jon Skeet 2/9/2012 #1

当引入 C# 5 CTP 时,您当然可以用 ...尽管这样做通常不是一个好主意。我相信 VS 2013 的发布改变了这一点,成为一个错误。async

除非您启动了任何其他前台线程,否则您的程序将在完成时退出,即使它已经开始了一些后台工作。Main

到底想做什么?请注意,您的方法目前确实不需要异步 - 它无缘无故地添加了一个额外的层。它在逻辑上等同于(但比):GetList()

public Task<List<TvChannel>> GetList()
{
    return new GetPrograms().DownloadTvChannels();
}

评论

2赞 danielovich 2/9/2012
乔恩,我想异步获取列表中的项目,那么为什么异步不适合该 GetList 方法?是因为我需要收集列表异步中的项目而不是列表本身吗?当我尝试用异步标记 Main 方法时,我得到一个“不包含静态 Main 方法...”
0赞 Jon Skeet 2/9/2012
@danielovich:返回什么?大概它返回一个,不是吗?如果没有,您不太可能等待它。(考虑到 awaiter 模式,可能,但不太可能。至于方法 - 它仍然需要是静态的......您是否将修饰符替换为修饰符?DownloadTvChannels()Task<List<TvChannel>>Mainstaticasync
0赞 danielovich 2/9/2012
是的,它返回一个 Task<..>就像你说的。无论我如何尝试将异步放入 Main 方法签名中,它都会引发错误。我坐在 VS11 预览位上!
0赞 Jon Skeet 2/9/2012
@danielovich:即使使用void返回类型?只?但是,如果已经返回 ,大概它已经是异步的 - 所以你不需要添加另一个层。值得仔细理解这一点。public static async void Main() {}DownloadTvChannels()Task<List<TvChannel>>
1赞 Jon Skeet 11/21/2016
@nawfal:回想起来,我认为在VS2013发布之前,情况发生了变化。不确定 C# 7 是否会改变这一点......
541赞 Stephen Cleary 2/9/2012 #2

在 Visual Studio 2012 中,编译器将禁止使用某个方法。在 Visual Studio 2010 中,使用异步 CTP 允许(但从不推荐)这样做。async Main

从 Visual Studio 2017 Update 3 (15.3) 开始,该语言现在支持 - 只要它返回 或 .因此,您现在可以这样做:async MainTaskTask<T>

class Program
{
    static async Task Main(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        var list = await bs.GetList();
    }
}

语义似乎与阻塞主线程的样式相同。但是,目前还没有针对 C# 7.1 的语言规范,因此这只是一个假设。GetAwaiter().GetResult()


我有关于异步/await和异步控制台程序的博客文章。以下是介绍文章中的一些背景信息:

如果“await”看到 awaitable 尚未完成,则它异步执行操作。它告诉 awaitable 在完成时运行该方法的其余部分,然后从 async 方法返回。Await 还将在将方法的其余部分传递给 awaitable 时捕获当前上下文

稍后,当 awaitable 完成时,它将执行 async 方法的其余部分(在捕获的上下文中)。

这就是为什么这在控制台程序中是一个问题:async Main

请记住,在我们的介绍文章中,异步方法将在完成之前返回给其调用方。这在 UI 应用程序(该方法只是返回到 UI 事件循环)和 ASP.NET 应用程序(该方法返回线程但使请求保持活动状态)中非常有效。对于控制台程序来说,效果不太好:Main 返回操作系统 - 因此您的程序退出。

一种解决方案是提供自己的上下文 - 异步兼容的控制台程序的“主循环”。

如果计算机具有异步 CTP,则可以从“我的文档”\Microsoft Visual Studio 异步 CTP\Samples(C# 测试)单元测试\AsyncTestUtilities 中使用。或者,可以使用我的 Nito.AsyncEx NuGet 包中的 AsyncContextGeneralThreadAffineContext

下面是一个使用 ; 具有几乎相同的用法:AsyncContextGeneralThreadAffineContext

using Nito.AsyncEx;
class Program
{
    static void Main(string[] args)
    {
        AsyncContext.Run(() => MainAsync(args));
    }

    static async void MainAsync(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        var list = await bs.GetList();
    }
}

或者,您可以直接阻止主控制台线程,直到异步工作完成:

class Program
{
    static void Main(string[] args)
    {
        MainAsync(args).GetAwaiter().GetResult();
    }

    static async Task MainAsync(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        var list = await bs.GetList();
    }
}

注意 ;这样可以避免使用 或 时发生的换行。GetAwaiter().GetResult()AggregateExceptionWait()Result

评论

36赞 Stephen Cleary 5/22/2014
您可以使用简单的 或 ,这没有错。但请注意,有两个重要的区别:1) 所有延续都在线程池上运行,而不是在主线程上运行,以及 2) 任何异常都包装在 .WaitResultasyncAggregateException
3赞 ConstantineK 1/5/2016
在这篇文章(和你的博客文章)之前,我遇到了一个真正的问题。这是迄今为止解决此问题的最简单方法,您只需使用“install-package Nito.Asyncex”即可在 nuget 控制台中安装包,然后就完成了。
1赞 Greg 8/22/2017
我对这些解决方案的问题是调试器(Visual Studio 2017 15.3.1)在Main方法中中断,而不是在引发异常的位置中断,并且“调用堆栈”窗口为空。
10赞 Mafii 11/30/2017
C# 7.1 现在有一个异步主数据库,可能值得添加到您的好答案中,@StephenCleary github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/......
3赞 Liam 7/9/2018
如果您在 VS 2017 中使用 C# 7.1 版本,我需要通过添加到 csproj 文件中来确保项目配置为使用最新版本的语言,如下所示<LangVersion>latest</LangVersion>
92赞 Steven Evers 7/11/2013 #3

您也可以通过执行以下操作来执行此操作,而无需外部库:

class Program
{
    static void Main(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        var getListTask = bs.GetList(); // returns the Task<List<TvChannel>>

        Task.WaitAll(getListTask); // block while the task completes

        var list = getListTask.Result;
    }
}

评论

7赞 do0g 7/12/2014
请记住,这也是一个阻塞调用,因此可以在没有 .getListTask.ResultTask.WaitAll(getListTask)
28赞 do0g 7/12/2014
此外,如果抛出,则必须捕获并询问其异常,以确定抛出的实际异常。但是,您可以调用以获取 ,并调用它,即 。当从 (也是阻塞调用)获取结果时,抛出的任何异常都不会包装在 .GetListAggregateExceptionGetAwaiter()TaskAwaiterTaskGetResult()var list = getListTask.GetAwaiter().GetResult();TaskAwaiterAggregateException
2赞 Deathstalker 1/24/2017
.GetAwaiter() 中。GetResult 是我需要的答案。这非常适合我试图做的事情。我可能也会在其他地方使用它。
3赞 mysticdotnet 2/7/2014 #4

在 Main 中,尝试将对 GetList 的调用更改为:

Task.Run(() => bs.GetList());
6赞 user3408030 3/12/2014 #5

要从 Main 异步调用任务,请使用

  1. 适用于 .NET 4.5 的 Task.Run()

  2. Task.Factory.StartNew() for .NET 4.0 (可能需要 Microsoft.Bcl.Async 库作为 async 和 await 关键字)

细节: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx

393赞 Chris Moschini 7/7/2014 #6

您可以使用以下简单构造来解决这个问题:

class Program
{
    static void Main(string[] args)
    {
        Task.Run(async () =>
        {
            // Do any async anything you need here without worry
        }).GetAwaiter().GetResult();
    }
}

这会将您所做的一切放在 ThreadPool 上您想要的位置(因此您启动/等待的其他任务不会尝试重新加入它们不应该加入的线程),并等到所有操作完成后再关闭控制台应用程序。不需要特殊的循环或外部库。

编辑:合并安德鲁的未捕获异常解决方案。

评论

3赞 abatishchev 1/29/2015
这种方法非常明显,但往往会包装异常,所以我现在正在寻找更好的方法。
2赞 Chris Moschini 3/24/2015
@abatishchev 你应该在代码中使用 try/catch,至少在 Task.Run 中,如果不是更精细的话,不要让异常浮动到 Task。你可以通过把try/catch放在可能失败的事情上来避免总结问题。
57赞 Andrew Arnott 11/20/2015
如果你替换成,你会在事情抛出时避开包装器。Wait()GetAwaiter().GetResult()AggregateException
7赞 user9993 6/14/2017
在撰写本文时,这就是 C# 7.1 中的引入方式。async main
0赞 Sinjai 1/18/2019
@user9993 根据这个提议,这并不完全正确。
18赞 Johan Falk 9/3/2014 #7

还没有需要这么多,但是当我使用控制台应用程序进行快速测试并需要异步时,我只是这样解决了它:

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

    static async Task MainAsync(string[] args)
    {
        // Code here
    }
}

评论

0赞 Manushin Igor 9/23/2015
如果你必须将任务调度到当前上下文,然后等待它(例如,你可能忘记添加 ConfigureAwait(false),所以 return 方法将被调度到主线程中,该线程位于 Wait 函数中,此示例将工作错误。由于当前线程处于等待状态,因此您将收到死锁。
7赞 Andrew Arnott 11/20/2015
不对,@ManushinIgor。至少在这个微不足道的例子中,没有与主线程相关联。所以它不会死锁,因为即使没有,所有延续都将在线程池上执行。SynchronizationContextConfigureAwait(false)
83赞 Cory Nelson 9/3/2014 #8

我将添加一个所有其他答案都忽略的重要功能:取消。

TPL 中的一大亮点是取消支持,控制台应用程序内置了取消方法 (CTRL+C)。将它们绑定在一起非常简单。这是我构建所有异步控制台应用程序的方式:

static void Main(string[] args)
{
    CancellationTokenSource cts = new CancellationTokenSource();
    
    System.Console.CancelKeyPress += (s, e) =>
    {
        e.Cancel = true;
        cts.Cancel();
    };

    MainAsync(args, cts.Token).GetAwaiter.GetResult();
}

static async Task MainAsync(string[] args, CancellationToken token)
{
    ...
}

评论

1赞 Siewers 10/15/2014
取消令牌是否也应该传递给 ?Wait()
5赞 Cory Nelson 10/15/2014
否,因为您希望异步代码能够正常处理取消。如果将它传递给 ,它不会等待异步代码完成 -- 它将停止等待并立即结束该过程。Wait()
0赞 Siewers 10/15/2014
你确定吗?我刚刚尝试了一下,似乎正在最深层次处理取消请求,即使该方法传递了相同的令牌。我想说的是,这似乎没有任何区别。Wait()
4赞 Cory Nelson 10/15/2014
我确定。您希望取消运算本身,而不是等待运算完成。除非你不关心清理代码的完成或它的结果。
1赞 Siewers 10/15/2014
是的,我想我明白了,它似乎对我的代码没有任何影响。另一件让我偏离正轨的事情是 ReSharper 关于支持取消的等待方法的礼貌提示;)您可能希望在示例中包含 try catch,因为它会抛出 OperationCancelledException,我一开始无法弄清楚
0赞 Nathan Phillips 4/7/2015 #9

为了避免在调用堆栈中的某个位置调用尝试重新加入当前线程(卡在等待中)的函数时冻结,您需要执行以下操作:

class Program
{
    static void Main(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        List<TvChannel> list = Task.Run((Func<Task<List<TvChannel>>>)bs.GetList).Result;
    }
}

(演员表仅用于解决歧义)

评论

0赞 Stefano d'Antonio 1/31/2016
谢谢;Task.Run 不会导致 GetList() 的死锁。等等,这个答案应该有更多的赞成票......
2赞 DavidRR 5/17/2016 #10

在 MSDN 上,Task.Run 方法 (Action) 的文档提供了此示例,该示例演示如何从以下位置异步运行方法:main

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
    public static void Main()
    {
        ShowThreadInfo("Application");

        var t = Task.Run(() => ShowThreadInfo("Task") );
        t.Wait();
    }

    static void ShowThreadInfo(String s)
    {
        Console.WriteLine("{0} Thread ID: {1}",
                          s, Thread.CurrentThread.ManagedThreadId);
    }
}
// The example displays the following output:
//       Application thread ID: 1
//       Task thread ID: 3

请注意以下示例后面的以下语句:

这些示例显示异步任务在不同的 线程比主应用程序线程。

因此,如果您希望任务在主应用程序线程上运行,请参阅@StephenCleary答案

关于运行任务的线程,还要注意 Stephen 对他的答案的评论

您可以使用简单的 或 ,这没有错 有了那个。但请注意,有两个重要的区别:1) 所有延续都在线程池上运行,而不是在主线程池上运行 线程,并且 2) 任何异常都包含在 .WaitResultasyncAggregateException

(请参阅异常处理(任务并行库),了解如何合并异常处理来处理 。AggregateException


最后,在 MSDN 上,从 Task.Delay 方法 (TimeSpan) 的文档中,此示例演示如何运行返回值的异步任务:

using System;
using System.Threading.Tasks;

public class Example
{
    public static void Main()
    {
        var t = Task.Run(async delegate
                {
                    await Task.Delay(TimeSpan.FromSeconds(1.5));
                    return 42;
                });
        t.Wait();
        Console.WriteLine("Task t Status: {0}, Result: {1}",
                          t.Status, t.Result);
    }
}
// The example displays the following output:
//        Task t Status: RanToCompletion, Result: 42

请注意,您可以像这样传递 lambda 函数,而不是将 传递给 。delegateTask.Run

var t = Task.Run(async () =>
        {
            await Task.Delay(TimeSpan.FromSeconds(1.5));
            return 42;
        });
2赞 user_v 7/26/2016 #11

就我而言,我有一个作业列表,我想从我的主方法异步运行,已经在生产中使用它很长一段时间并且工作正常。

static void Main(string[] args)
{
    Task.Run(async () => { await Task.WhenAll(jobslist.Select(nl => RunMulti(nl))); }).GetAwaiter().GetResult();
}
private static async Task RunMulti(List<string> joblist)
{
    await ...
}
21赞 Şafak Gür 9/6/2016 #12

如果您使用的是 C# 7.1 或更高版本,请使用 nawpal 的答案,只需将 Main 方法的返回类型更改为 或 。如果您不是:TaskTask<int>

  • 有一个像约翰说的那样。async Task MainAsync
  • 调用它来捕获潜在的异常,就像 do0g 所说的那样。.GetAwaiter().GetResult()
  • 像科里说的那样支持取消。
  • 第二个应立即终止该过程。(谢谢binkiCTRL+C
  • 句柄 - 返回相应的错误代码。OperationCancelledException

最终代码如下所示:

private static int Main(string[] args)
{
    var cts = new CancellationTokenSource();
    Console.CancelKeyPress += (s, e) =>
    {
        e.Cancel = !cts.IsCancellationRequested;
        cts.Cancel();
    };

    try
    {
        return MainAsync(args, cts.Token).GetAwaiter().GetResult();
    }
    catch (OperationCanceledException)
    {
        return 1223; // Cancelled.
    }
}

private static async Task<int> MainAsync(string[] args, CancellationToken cancellationToken)
{
    // Your code...

    return await Task.FromResult(0); // Success.
}

评论

2赞 binki 3/1/2018
许多不错的程序只会在第一次取消 CancelKeyPress,因此,如果您按 ^C 一次,您可以正常关闭,但如果您不耐烦,第二个 ^C 会不正常地终止。使用此解决方案时,如果程序无法遵循 CancellationToken,则需要手动终止程序,因为这是无条件的。e.Cancel = true
91赞 nawfal 5/30/2017 #13

在 C# 7.1 中,您将能够执行适当的异步 Main。方法的相应签名已扩展到:Main

public static Task Main();
public static Task<int> Main();
public static Task Main(string[] args);
public static Task<int> Main(string[] args);

例如,您可以执行以下操作:

static async Task Main(string[] args)
{
    Bootstrapper bs = new Bootstrapper();
    var list = await bs.GetList();
}

在编译时,异步入口点方法将被转换为调用。GetAwaitor().GetResult()

细节: https://blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main

编辑:

要启用 C# 7.1 语言功能,您需要右键单击项目并单击“属性”,然后转到“生成”选项卡。在那里,单击底部的高级按钮:

enter image description here

从语言版本下拉菜单中,选择“7.1”(或任何更高的值):

enter image description here

默认值为“最新主要版本”,在撰写本文时,该版本将评估为 C# 7.0,该版本不支持控制台应用中的异步 main。

评论

2赞 Mahmoud Al-Qudsi 7/11/2017
FWIW 这在 Visual Studio 15.3 及更高版本中可用,目前可从以下位置作为 beta/预览版提供: visualstudio.com/vs/preview
0赞 11/16/2017
等一会。。。我正在运行完全更新的安装,我的最新选项是 7.1...你怎么在 5 月份就已经拿到 7.2 了?
0赞 nawfal 11/16/2017
五月的答案是我的。10 月份的编辑是由其他人编辑的,当时我认为 7.2(预览版?)可能已经发布。
0赞 Nick N. 5/14/2018
有关示例,请查看此 GitHub 存储库:For an example have a look at this GitHub repo: github.com/nicknijenhuis/ChromeCastBackgroundSyncer
1赞 user230910 8/22/2018
注意 - 检查它是否在所有配置上,而不仅仅是在执行此操作时进行调试!
5赞 Kedrzu 8/22/2017 #14

最新版本的 C# - C# 7.1 允许创建异步控制台应用。若要在项目中启用 C# 7.1,必须将 VS 升级到至少 15.3,并将 C# 版本更改为 或 。为此,请转到“项目属性”->“生成”-“高级”>“高级”-“>语言版本”。C# 7.1C# latest minor version

在此之后,以下代码将起作用:

internal class Program
{
    public static async Task Main(string[] args)
    {
         (...)
    }
27赞 M.Hassan 9/19/2017 #15

C# 7.1(使用 vs 2017 Update 3)引入了:async Main

你可以写:

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

有关更多详细信息,请参阅 C# 7 系列,第 2 部分:异步主

更新:

您可能会收到编译错误:

程序不包含适合入口点的静态“Main”方法

此错误是由于 vs2017.3 默认配置为 c#7.0 而不是 c#7.1。

应显式修改项目的设置以设置 c#7.1 功能。

可以通过两种方法设置 c#7.1:

方法 1:使用项目设置窗口:

  • 打开项目的设置
  • 选择“生成”选项卡
  • 单击“高级”按钮
  • 选择所需的版本 如下图所示:

enter image description here

方法 2:手动修改 .csproj 的 PropertyGroup

添加此属性:

<LangVersion>7.1</LangVersion>

例:

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <Prefer32Bit>false</Prefer32Bit>
    <LangVersion>7.1</LangVersion>
</PropertyGroup>
-1赞 Kristaps Koks 8/4/2020 #16
class Program
{
     public static EventHandler AsyncHandler;
     static void Main(string[] args)
     {
        AsyncHandler+= async (sender, eventArgs) => { await AsyncMain(); };
        AsyncHandler?.Invoke(null, null);
     }

     private async Task AsyncMain()
     {
        //Your Async Code
     }
}
4赞 DanKodi 7/27/2021 #17

从 C# 7.1 开始,以下签名对 Main 方法有效。

public static void Main() { }
public static int Main() { }
public static void Main(string[] args) { }
public static int Main(string[] args) { }
public static async Task Main() { }
public static async Task<int> Main() { }
public static async Task Main(string[] args) { }
public static async Task<int> Main(string[] args) { }

所以,现在你可以执行 async/await

static async Task Main(string[] args)
{
    Console.WriteLine("Hello Asyn Main method!");
    await Task.Delay(200);
}
-1赞 Tom Charles Zhang 7/8/2022 #18

这是假设的,但我在想:

static void Main(string[] args)
{
    var context = new Thread(() => /*do stuff*/);
    context.Start();
    context.Join();
}
-1赞 Andrew Gale 10/7/2022 #19

不确定这是否是您要找的,但我想等待加载的方法。我最终使用了Main_Shown处理程序并使其异步:

private async void Main_Shown(object sender, EventArgs e)
{
   await myAsyncMethod();
}