首页 文章

可以't specify the ' async ' modifier on the '控制台应用的主要方法

提问于
浏览
345

我是使用 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();
    }
}

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

15 回答

  • 3

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

    class Program
    {
        static void Main(string[] args)
        {
            MainAsync(args).Wait();
        }
    
        static async Task MainAsync(string[] args)
        {
            // Code here
        }
    }
    
  • 1

    如果您使用的是C#7.1或更高版本,请使用nawfal's answer并将Main方法的返回类型更改为 TaskTask<int> . 如果你不是:

    • async Task MainAsync like Johan said .

    • 调用其 .GetAwaiter().GetResult() 来捕获基础异常like do0g said .

    • 支持取消like Cory said .

    • 第二个 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.
    }
    
  • 18

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

    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;
        }
    }
    
  • 53

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

    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)
    {
        ...
    }
    
  • 316

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

    我最近有关于async/awaitasynchronous console programs的博客文章 . 以下是介绍帖子的一些背景信息:

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

    这就是为什么这是带有 async Main 的控制台程序中的问题:

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

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

    如果您的计算机具有Async CTP,则可以使用My Documents \ Microsoft Visual Studio异步CTP \ Samples(C#Testing)单元测试\ AsyncTestUtilities中的 GeneralThreadAffineContext . 或者,您可以使用my Nito.AsyncEx NuGet package来自my Nito.AsyncEx NuGet package .

    这是一个使用 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();
        }
    }
    

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

    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() ;这可以避免在使用 Wait()Result 时发生的 AggregateException 包装 .

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

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

  • 70

    当引入C#5 CTP时,你肯定可以用 async 标记Main ...虽然这样做通常不是一个好主意 . 我相信这已经被VS 2013的发布改变成了一个错误 .

    除非你已经启动了任何其他前台线程,否则当 Main 完成时,你的程序将会退出,即使它已经启动了一些后台工作 .

    你真的想做什么?请注意,您的 GetList() 方法确实没有添加额外的图层,没有任何实际原因 . 它在逻辑上等同于(但更复杂):

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

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

    • 用于.NET 4.5的Task.Run()

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

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

  • 88

    在MSDN上,Task.Run Method (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
    

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

    示例显示异步任务在与主应用程序线程不同的线程上执行 .

    因此,如果您希望任务在主应用程序线程上运行,请参见the answer by @StephenCleary .

    关于任务运行的线程,请注意Stephen的comment关于他的回答:

    您可以使用简单的等待或结果,并且没有任何问题 . 但请注意,存在两个重要区别:1)所有异步延续都在线程池而不是主线程上运行,以及2)任何异常都包含在AggregateException中 .

    (有关如何合并异常处理以处理 AggregateException ,请参阅Exception Handling (Task Parallel Library) . )


    最后,在Task.Delay Method (TimeSpan)文档的MSDN上,此示例显示了如何运行返回值的异步任务:

    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函数,而不是将 delegate 传递给 Task.Run

    var t = Task.Run(async () =>
            {
                await Task.Delay(TimeSpan.FromSeconds(1.5));
                return 42;
            });
    
  • 290

    在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,它不支持控制台应用程序中的异步主 .

  • 16

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

    Task.Run(() => bs.GetList());
    
  • 4

    为了避免在调用函数某处向某个地方调用函数时出现冻结,该函数尝试重新加入当前线程(卡在等待中),您需要执行以下操作:

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

    (演员只需解决歧义)

  • 7

    在我的情况下,我有一个我希望从我的main方法异步运行的作业列表,已经在 生产环境 中使用了很长一段时间并且工作正常 .

    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 ...
    }
    
  • 4

    最新版本的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)
        {
             (...)
        }
    
  • 16

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

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

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

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

  • 1

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

    你可以写:

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

    欲了解更多详情C# 7 Series, Part 2: Async Main

    Update:

    您可能会收到编译错误:

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

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

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

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

    Method 1: Using the project settings window:

    • 打开项目的设置

    • 选择“构建”选项卡

    • 单击“高级”按钮

    • 选择所需的版本如下图所示:

    enter image description here

    Method2: Modify PropertyGroup of .csproj manually

    添加此属性:

    <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>
    

相关问题