无法在控制台应用程序的"Main"方法上指定"async"修饰符

dan*_*ich 415 .net c# asynchronous console-application

我是使用async修饰符进行异步编程的新手.我试图弄清楚如何确保我Main的控制台应用程序的方法实际上异步运行.

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我知道这不是从"顶部"异步运行的.由于无法asyncMain方法上指定修饰符,如何在main异步中运行代码?

Ste*_*ary 354

正如您所发现的,在VS11中,编译器将禁止使用某种async Main方法.在VS2010中使用Async CTP允许(但从未推荐).

我最近有关于async/await异步控制台程序的博客文章.以下是介绍帖子的一些背景信息:

如果"等待"看到等待未完成,那么它就是异步行为.它告诉等待在完成时运行方法的其余部分,然后从异步方法返回.当Await将方法的其余部分传递给等待时,Await 也将捕获当前上下文.

稍后,当等待完成时,它将执行异步方法的剩余部分(在捕获的上下文中).

这就是为什么这是Console程序中的一个问题async Main:

请记住,在我们的介绍文章中,异步方法将在完成之前返回其调用方.这在UI应用程序中完美运行(该方法只返回到UI事件循环)和ASP.NET应用程序(该方法返回线程但保持请求处于活动状态).它对Console程序来说效果不佳:主要返回操作系统 - 所以程序退出.

一种解决方案是提供您自己的上下文 - 控制台程序的"主循环",它是异步兼容的.

如果您有与异步CTP机器,你可以使用GeneralThreadAffineContext我的文档\微软的Visual Studio异步CTP \样品(C#测试)单元测试\ AsyncTestUtilities.另外,您也可以使用AsyncContext我Nito.AsyncEx NuGet包.

这是一个使用的例子AsyncContext; GeneralThreadAffineContext具有几乎相同的用法:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

注意使用GetAwaiter().GetResult(); 这可以避免在AggregateException使用Wait()或时发生的包装Result.

更新,2017-11-30:从Visual Studio 2017 Update 3(15.3)开始,该语言现在支持async Main- 只要它返回TaskTask<T>.所以你现在可以这样做:

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

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

  • 你*可以*使用一个简单的`Wait`或`Result`,并且没有任何问题.但请注意,有两个重要的区别:1)所有`async`延续都在线程池而不是主线程上运行,2)任何异常都包含在`AggregateException`中. (28认同)
  • C#7.1现在有一个异步主,可能值得添加到你的好答案,@ StephenCleary https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/async-main.md (6认同)
  • 如果您在VS 2017中使用C#7.1版本,我需要确保项目配置为使用最新版本的语言,将"<LangVersion> latest </ LangVersion>"添加到csproj文件中,如[此处所示](https ://stackoverflow.com/a/45958948/542251). (3认同)
  • 直到这个(和你的博客文章)有一个真正的问题解决这个问题.到目前为止,这是解决此问题的最简单方法,您可以使用"install-package Nito.Asyncex"在nuget控制台中安装软件包,您就完成了. (2认同)

Chr*_*ini 351

你可以用这个简单的结构解决这个问题:

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

这将把你所做的一切都放在你想要它的ThreadPool上(所以你开始/等待的其他任务不会尝试重新加入他们不应该的线程),并等到关闭Console应用程序之前完成所有操作.不需要特殊循环或外部库.

编辑:将Andrew的解决方案纳入未捕获的异常.

  • 如果用`GetAwaiter()替换`Wait()`.GetResult()`当事情抛出时你将避免使用`AggregateException`包装器. (53认同)
  • 这就是在撰写本文时,在C#7.1中引入`async main`的方法. (7认同)
  • 这种方法非常明显,但往往会包含异常,所以我现在正在寻找更好的方法. (3认同)
  • @abatishchev您应该在代码中使用try/catch,至少在Task.Run中如果不是更精细,不要让异常浮动到任务.你可以通过将try/catch放在可能失败的东西上来避免包装问题. (2认同)

Ste*_*ers 90

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 此外,如果`GetList`抛出,则必须捕获`AggregateException`并询问其异常以确定抛出的实际异常.但是,您可以调用`GetAwaiter()`来获取`Task`的`TaskAwaiter`,并在其上调用`GetResult()`,即`var list = getListTask.GetAwaiter().GetResult();`.从`TaskAwaiter`(也是一个阻塞调用)获得结果时,抛出的任何异常都不会包含在`AggregateException`中. (27认同)
  • 请记住,`getListTask.Result`也是一个阻塞调用,因此上面的代码可以在没有`Task.WaitAll(getListTask)`的情况下编写. (7认同)
  • .GetAwaiter().GetResult 是我需要的答案。这非常适合我试图做的事情。我可能也会在其他地方使用它。 (2认同)

Cor*_*son 74

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

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

static async Task MainAsync(string[] args, CancellationToken token)
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

  • 不,因为您希望异步代码能够优雅地处理取消.如果将它传递给`Wait()`,它将不会等待异步代码完成 - 它将停止等待并立即结束该过程. (5认同)
  • 我确定.你想取消操作本身,而不是等待操作完成.除非你不关心清理代码整理或结果. (4认同)

naw*_*fal 71

在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);
Run Code Online (Sandbox Code Playgroud)

例如,你可能会这样做:

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

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

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

编辑:

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

在此输入图像描述

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

在此输入图像描述

默认为"最新主要版本",它将评估(在撰写本文时)C#7.0,它不支持控制台应用程序中的异步主.

  • FWIW可在Visual Studio 15.3及更高版本中使用,目前可从此处获得测试版/预览版:https://www.visualstudio.com/vs/preview/ (2认同)

Joh*_*alk 19

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

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

    static async Task MainAsync(string[] args)
    {
        // Code here
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 不是真的,@ ManushinIgor.至少在这个简单的例子中,没有与主线程相关联的`SynchronizationContext`.所以它不会死锁,因为即使没有`ConfigureAwait(false)`,所有的连续都将在线程池上执行. (6认同)

M.H*_*san 19

C#7.1(使用vs 2017更新3)引入了async main

你可以写:

   static async Task Main(string[] args)
  {
    await ...
  }
Run Code Online (Sandbox Code Playgroud)

有关详细信息,请参阅C#7系列,第2部分:Async Main

更新:

您可能会收到编译错误:

程序不包含适用于入口点的静态"Main"方法

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

您应该明确修改项目的设置以设置c#7.1功能.

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

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

  • 打开项目的设置
  • 选择"构建"选项卡
  • 单击"高级"按钮
  • 选择所需的版本如下图所示:

在此输入图像描述

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

添加此属性:

    <LangVersion>7.1</LangVersion>
Run Code Online (Sandbox Code Playgroud)

例:

    <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>    
Run Code Online (Sandbox Code Playgroud)


Şaf*_*Gür 18

如果您使用C#7.1或更高版本,去与nawfal的回答,只是改变你的主要方法的返回类型TaskTask<int>.如果你不是:

  • async Task MainAsync 像约翰说那样.
  • 调用它.GetAwaiter().GetResult()来捕获像do0g所说的底层异常.
  • 支持取消像科里说.
  • 第二个CTRL+C应立即终止该过程.(谢谢binki!)
  • 句柄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.
}
Run Code Online (Sandbox Code Playgroud)

  • 许多不错的程序只会在第一次取消 CancelKeyPress,这样,如果您按 ^C 一次,您就会正常关闭,但如果您不耐烦,第二次 ^C 就会不正常地终止。使用此解决方案,如果程序无法遵守 CancellationToken,则需要手动终止该程序,因为“e.Cancel = true”是无条件的。 (2认同)

use*_*030 7

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

  1. .NET 4.5的Task.Run()

  2. .NET 4.0的Task.Factory.StartNew()(可能需要Microsoft.Bcl.Async库用于异步和等待关键字)

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


Ked*_*rzu 5

最新版本的 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)
    {
         (...)
    }
Run Code Online (Sandbox Code Playgroud)