提问人:danielovich 提问时间:2/9/2012 最后编辑:Liamdanielovich 更新时间:10/30/2023 访问量:309776
无法在控制台应用的“Main”方法上指定“async”修饰符
Can't specify the 'async' modifier on the 'Main' method of a console app
问:
我是使用修饰符进行异步编程的新手。我正在尝试弄清楚如何确保我的控制台应用程序方法实际上异步运行。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
答:
当引入 C# 5 CTP 时,您当然可以用 ...尽管这样做通常不是一个好主意。我相信 VS 2013 的发布改变了这一点,成为一个错误。async
除非您启动了任何其他前台线程,否则您的程序将在完成时退出,即使它已经开始了一些后台工作。Main
你到底想做什么?请注意,您的方法目前确实不需要异步 - 它无缘无故地添加了一个额外的层。它在逻辑上等同于(但比):GetList()
public Task<List<TvChannel>> GetList()
{
return new GetPrograms().DownloadTvChannels();
}
评论
DownloadTvChannels()
Task<List<TvChannel>>
Main
static
async
public static async void Main() {}
DownloadTvChannels()
Task<List<TvChannel>>
在 Visual Studio 2012 中,编译器将禁止使用某个方法。在 Visual Studio 2010 中,使用异步 CTP 允许(但从不推荐)这样做。async Main
从 Visual Studio 2017 Update 3 (15.3) 开始,该语言现在支持 - 只要它返回 或 .因此,您现在可以这样做:async Main
Task
Task<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 包中的 AsyncContext
。GeneralThreadAffineContext
下面是一个使用 ; 具有几乎相同的用法: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()
AggregateException
Wait()
Result
评论
Wait
Result
async
AggregateException
<LangVersion>latest</LangVersion>
您也可以通过执行以下操作来执行此操作,而无需外部库:
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;
}
}
评论
getListTask.Result
Task.WaitAll(getListTask)
GetList
AggregateException
GetAwaiter()
TaskAwaiter
Task
GetResult()
var list = getListTask.GetAwaiter().GetResult();
TaskAwaiter
AggregateException
在 Main 中,尝试将对 GetList 的调用更改为:
Task.Run(() => bs.GetList());
要从 Main 异步调用任务,请使用
适用于 .NET 4.5 的 Task.Run()
Task.Factory.StartNew() for .NET 4.0 (可能需要 Microsoft.Bcl.Async 库作为 async 和 await 关键字)
细节: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
您可以使用以下简单构造来解决这个问题:
class Program
{
static void Main(string[] args)
{
Task.Run(async () =>
{
// Do any async anything you need here without worry
}).GetAwaiter().GetResult();
}
}
这会将您所做的一切放在 ThreadPool 上您想要的位置(因此您启动/等待的其他任务不会尝试重新加入它们不应该加入的线程),并等到所有操作完成后再关闭控制台应用程序。不需要特殊的循环或外部库。
编辑:合并安德鲁的未捕获异常解决方案。
评论
Wait()
GetAwaiter().GetResult()
AggregateException
async main
还没有需要这么多,但是当我使用控制台应用程序进行快速测试并需要异步时,我只是这样解决了它:
class Program
{
static void Main(string[] args)
{
MainAsync(args).Wait();
}
static async Task MainAsync(string[] args)
{
// Code here
}
}
评论
SynchronizationContext
ConfigureAwait(false)
我将添加一个所有其他答案都忽略的重要功能:取消。
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)
{
...
}
评论
Wait()
Wait()
Wait()
为了避免在调用堆栈中的某个位置调用尝试重新加入当前线程(卡在等待中)的函数时冻结,您需要执行以下操作:
class Program
{
static void Main(string[] args)
{
Bootstrapper bs = new Bootstrapper();
List<TvChannel> list = Task.Run((Func<Task<List<TvChannel>>>)bs.GetList).Result;
}
}
(演员表仅用于解决歧义)
评论
在 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) 任何异常都包含在 .
Wait
Result
async
AggregateException
(请参阅异常处理(任务并行库),了解如何合并异常处理来处理 。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 函数,而不是将 传递给 。delegate
Task.Run
var t = Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(1.5));
return 42;
});
就我而言,我有一个作业列表,我想从我的主方法异步运行,已经在生产中使用它很长一段时间并且工作正常。
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 ...
}
如果您使用的是 C# 7.1 或更高版本,请使用 nawpal 的答案,只需将 Main 方法的返回类型更改为 或 。如果您不是:Task
Task<int>
- 有一个像约翰说的那样。
async Task MainAsync
- 调用它来捕获潜在的异常,就像 do0g 所说的那样。
.GetAwaiter().GetResult()
- 像科里说的那样支持取消。
- 第二个应立即终止该过程。(谢谢binki!
CTRL+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.
}
评论
e.Cancel = true
在 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 语言功能,您需要右键单击项目并单击“属性”,然后转到“生成”选项卡。在那里,单击底部的高级按钮:
从语言版本下拉菜单中,选择“7.1”(或任何更高的值):
默认值为“最新主要版本”,在撰写本文时,该版本将评估为 C# 7.0,该版本不支持控制台应用中的异步 main。
评论
最新版本的 C# - C# 7.1 允许创建异步控制台应用。若要在项目中启用 C# 7.1,必须将 VS 升级到至少 15.3,并将 C# 版本更改为 或 。为此,请转到“项目属性”->“生成”-“高级”>“高级”-“>语言版本”。C# 7.1
C# latest minor version
在此之后,以下代码将起作用:
internal class Program
{
public static async Task Main(string[] args)
{
(...)
}
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:使用项目设置窗口:
- 打开项目的设置
- 选择“生成”选项卡
- 单击“高级”按钮
- 选择所需的版本 如下图所示:
方法 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>
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
}
}
从 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);
}
这是假设的,但我在想:
static void Main(string[] args)
{
var context = new Thread(() => /*do stuff*/);
context.Start();
context.Join();
}
不确定这是否是您要找的,但我想等待加载的方法。我最终使用了Main_Shown处理程序并使其异步:
private async void Main_Shown(object sender, EventArgs e)
{
await myAsyncMethod();
}
评论