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

时间:2012-02-09 10:13:37

标签: c# .net 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();
    }
}

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

17 个答案:

答案 0 :(得分:340)

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

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

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

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

答案 1 :(得分:319)

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

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

  

如果“await”看到等待状态尚未完成,则它会异步进行。它告诉等待在完成时运行方法的其余部分,然后从异步方法返回。当Await将方法的其余部分传递给等待时,Await也将捕获当前的上下文

     

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

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

  

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

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

如果您的计算机具有Async CTP,则可以使用我的文档\ Microsoft Visual Studio异步CTP \ Samples(C#测试)单元测试\ AsyncTestUtilities 中的GeneralThreadAffineContext。或者,您可以使用AsyncContext中的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();如果您使用AggregateExceptionWait(),这可以避免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();
    }
}

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

答案 2 :(得分: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;
    }
}

答案 3 :(得分: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)
{
    ...
}

答案 4 :(得分:62)

在C#7.1中,您将能够正确执行 async 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语言功能,您需要右键单击该项目,然后单击&#34;属性&#34;然后去&#34; Build&#34;标签。在那里,单击底部的高级按钮:

enter image description here

从语言版本下拉菜单中选择&#34; 7.1&#34; (或任何更高的价值):

enter image description here

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

答案 5 :(得分:18)

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

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

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

答案 6 :(得分:17)

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

你可以写:

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

有关详细信息C# 7 Series, Part 2: Async Main

<强>更新

您可能会收到编译错误:

  

程序不包含适用于入口点的静态“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>    

答案 7 :(得分:17)

如果您使用的是C#7.1或更高版本,请使用nawfal's answer,然后将主要方法的返回类型更改为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.
}

答案 8 :(得分:7)

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

  1. .NET 4.5的Task.Run()

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

  3. 详细说明: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx

答案 9 :(得分:4)

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

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

答案 10 :(得分:4)

当引入C#5 CTP时,你肯定可能async标记Main ...虽然这样做通常不是一个好主意。我相信这会因为VS 2013的发布而改变,成为一个错误。

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

真正尝试做什么?请注意,您的GetList()方法目前不需要异步 - 它正在添加一个额外的层,没有任何实际原因。它在逻辑上等同于(但更复杂):

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

答案 11 :(得分:4)

最新版本的C# - C#7.1允许创建异步控制台应用程序。要在项目中启用C#7.1,您必须将VS升级到至少15.3,并将C#版本更改为C# 7.1C# latest minor version。为此,请转到项目属性 - &gt;构建 - &gt;高级 - &gt;语言版本。

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

internal class Program
{
    public static async Task Main(string[] args)
    {
         (...)
    }

答案 12 :(得分:3)

在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 @StephenCleary

关于任务运行的主题,请注意Stephen的comment回答:

  

可以使用简单的WaitResult,而且没有错   接着就,随即。但请注意,有两个重要的区别:1)   所有async延续都在线程池而不是主线程上运行   线程,以及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;
        });

答案 13 :(得分:1)

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

答案 14 :(得分:1)

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

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

答案 15 :(得分:1)

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

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

(演员表只需解决歧义)

答案 16 :(得分:0)

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