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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9208921/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 06:22:33  来源:igfitidea点击:

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

c#.netasynchronousconsole-application

提问by danielovich

I am new to asynchronous programming with the asyncmodifier. I am trying to figure out how to make sure that my Mainmethod of a console application actually runs asynchronously.

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

I know this is not running asynchronously from "the top." Since it is not possible to specify the asyncmodifier on the Mainmethod, how can I run code within mainasynchronously?

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

采纳答案by Stephen Cleary

As you discovered, in VS11 the compiler will disallow an async Mainmethod. This was allowed (but never recommended) in VS2010 with the Async CTP.

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

I have recent blog posts about async/awaitand asynchronous console programsin particular. Here's some background info from the intro post:

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

If "await" sees that the awaitable has not completed, then it acts asynchronously. It tells the awaitable to run the remainder of the method when it completes, and then returnsfrom the async method. Await will also capture the current contextwhen it passes the remainder of the method to the awaitable.

Later on, when the awaitable completes, it will execute the remainder of the async method (within the captured context).

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

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

Here's why this is a problem in Console programs with an async Main:

这就是为什么在带有 的控制台程序中出现问题的原因async Main

Remember from our intro post that an async method will returnto its caller before it is complete. This works perfectly in UI applications (the method just returns to the UI event loop) and ASP.NET applications (the method returns off the thread but keeps the request alive). It doesn't work out so well for Console programs: Main returns to the OS - so your program exits.

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

One solution is to provide your own context - a "main loop" for your console program that is async-compatible.

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

If you have a machine with the Async CTP, you can use GeneralThreadAffineContextfrom My Documents\Microsoft Visual Studio Async CTP\Samples(C# Testing) Unit Testing\AsyncTestUtilities. Alternatively, you can use AsyncContextfrom my Nito.AsyncEx NuGet package.

如果你有一台带有 Async CTP 的机器,你可以GeneralThreadAffineContextMy Documents\Microsoft Visual Studio Async CTP\Samples(C# Testing) Unit Testing\AsyncTestUtilities 使用。或者,您可以AsyncContext我的 Nito.AsyncEx NuGet 包中使用

Here's an example using AsyncContext; GeneralThreadAffineContexthas almost identical usage:

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

Alternatively, you can just block the main Console thread until your asynchronous work has completed:

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

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

Note the use of GetAwaiter().GetResult(); this avoids the AggregateExceptionwrapping that happens if you use Wait()or Result.

注意使用GetAwaiter().GetResult(); 这避免了AggregateException使用Wait()or时发生的换行Result

Update, 2017-11-30:As of Visual Studio 2017 Update 3 (15.3), the language now supports an async Main- as long as it returns Taskor Task<T>. So you can now do this:

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

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

The semantics appear to be the same as the GetAwaiter().GetResult()style of blocking the main thread. However, there's no language spec for C# 7.1 yet, so this is only an assumption.

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

回答by Jon Skeet

When the C# 5 CTP was introduced, you certainly couldmark Main with async... although it was generally not a good idea to do so. I believe this was changed by the release of VS 2013 to become an error.

当引入 C# 5 CTP 时,您当然可以async...标记 Main,尽管这样做通常不是一个好主意。我相信这已被 VS 2013 的发布更改为错误。

Unless you've started any other foregroundthreads, your program will exit when Maincompletes, even if it's started some background work.

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

What are you reallytrying to do? Note that your GetList()method really doesn't need to be async at the moment - it's adding an extra layer for no real reason. It's logically equivalent to (but more complicated than):

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

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

回答by Steven Evers

You can do this without needing external libraries also by doing the following:

您也可以通过执行以下操作,在不需要外部库的情况下执行此操作:

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

回答by mysticdotnet

In Main try changing the call to GetList to:

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

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

回答by user3408030

For asynchronously calling task from Main, use

对于从 Main 异步调用任务,请使用

  1. Task.Run() for .NET 4.5

  2. Task.Factory.StartNew() for .NET 4.0 (May require Microsoft.Bcl.Async library for async and await keywords)

  1. .NET 4.5 的 Task.Run()

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

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

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

回答by Chris Moschini

You can solve this with this simple construct:

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

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

That will put everything you do out on the ThreadPool where you'd want it (so other Tasks you start/await don't attempt to rejoin a Thread they shouldn't), and wait until everything's done before closing the Console app. No need for special loops or outside libs.

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

Edit: Incorporate Andrew's solution for uncaught Exceptions.

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

回答by Johan Falk

Haven't needed this much yet, but when I've used console application for Quick tests and required async I've just solved it like this:

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

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

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

回答by Cory Nelson

I'll add an important feature that all of the other answers have overlooked: cancellation.

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

One of the big things in TPL is cancellation support, and console apps have a method of cancellation built in (CTRL+C). It's very simple to bind them together. This is how I structure all of my async console apps:

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

回答by Nathan Phillips

To avoid freezing when you call a function somewhere down the call stack that tries to re-join the current thread (which is stuck in a Wait), you need to do the following:

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

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

(the cast is only required to resolve ambiguity)

(演员只需要解决歧义)

回答by DavidRR

On MSDN, the documentation for Task.Run Method (Action)provides this example which shows how to run a method asynchronously from main:

在 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

Note this statement that follows the example:

请注意此示例后面的语句:

The examples show that the asynchronous task executes on a different thread than the main application thread.

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

So, if instead you want the task to run on the main application thread, see the answerby @StephenCleary.

所以,如果不是你希望任务应用程序的主线程上运行,看到答案通过@StephenCleary

And regarding the thread on which the task runs, also note Stephen's commenton his answer:

关于任务运行的线程,还要注意斯蒂芬对他的回答的评论

You canuse a simple Waitor Result, and there's nothing wrong with that. But be aware that there are two important differences: 1) all asynccontinuations run on the thread pool rather than the main thread, and 2) any exceptions are wrapped in an AggregateException.

可以使用简单的WaitResult,这没有任何问题。但请注意,有两个重要区别:1) 所有async延续都在线程池而不是主线程上运行,以及 2) 任何异常都包含在AggregateException.

(See Exception Handling (Task Parallel Library)for how to incorporate exception handling to deal with an AggregateException.)

(有关如何合并异常处理以处理.),请参阅异常处理(任务并行库)AggregateException



Finally, on MSDN from the documentation for Task.Delay Method (TimeSpan), this example shows how to run an asynchronous task that returns a value:

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

Note that instead of passing a delegateto Task.Run, you can instead pass a lambda function like this:

请注意,您可以像这样传递一个 lambda 函数,而不是传递delegateto Task.Run

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