[英]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();
}
}
我知道这不是从“顶部”异步运行的。 由于无法在Main
方法上指定async
修饰符,我如何在main
中异步运行代码?
正如您所发现的,在 VS11 中,编译器将不允许async Main
方法。 在带有异步 CTP 的 VS2010 中,这是允许的(但从不推荐)。
2017 年 11 月 30 日更新:从 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();
}
}
语义似乎与阻塞主线程的GetAwaiter().GetResult()
样式相同。 但是,C# 7.1 还没有语言规范,所以这只是一个假设。
我最近发表了一些关于async/await和异步控制台程序的博文。 以下是介绍帖子中的一些背景信息:
如果 "await" 看到 awaitable 还没有完成,那么它会异步执行。 它告诉 awaitable 在完成时运行该方法的其余部分,然后从 async 方法返回。 Await 还将在将方法的其余部分传递给可等待对象时捕获当前上下文。
稍后,当 awaitable 完成时,它将执行 async 方法的其余部分(在捕获的上下文中)。
这就是为什么在带有async Main
的控制台程序中出现问题的原因:
请记住,在我们的介绍文章中,异步方法将在完成之前返回给它的调用者。 这在 UI 应用程序(该方法只返回 UI 事件循环)和 ASP.NET 应用程序(该方法从线程返回但保持请求活动)中非常有效。 对于控制台程序来说效果不是很好:Main 返回操作系统 - 所以你的程序退出。
一种解决方案是提供您自己的上下文 - 为您的控制台程序提供异步兼容的“主循环”。
如果您有一台带有 Async CTP 的机器,您可以使用My Documents\Microsoft Visual Studio Async CTP\Samples(C# Testing) Unit Testing\AsyncTestUtilities GeneralThreadAffineContext
的 GeneralThreadAffineContext。 或者,您可以使用我的 Nito.AsyncEx NuGet 包中的AsyncContext
。
这是一个使用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
包装。
你可以用这个简单的结构来解决这个问题:
class Program
{
static void Main(string[] args)
{
Task.Run(async () =>
{
// Do any async anything you need here without worry
}).GetAwaiter().GetResult();
}
}
这会将您所做的一切放在您想要的线程池上(因此您启动/等待的其他任务不会尝试重新加入他们不应该的线程),并等到一切都完成后再关闭控制台应用程序。 不需要特殊的循环或外部库。
编辑:合并 Andrew 针对未捕获异常的解决方案。
您也可以通过执行以下操作来完成此操作而无需外部库:
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;
}
}
在 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 语言功能,您需要右键单击项目并单击“属性”,然后转到“构建”选项卡。 在那里,单击底部的高级按钮:
从语言版本下拉菜单中,选择“7.1”(或任何更高的值):
默认值为“最新的主要版本”,它将评估(在撰写本文时)C# 7.0,它不支持控制台应用程序中的异步主版本。
我将添加一个所有其他答案都忽略的重要功能:取消。
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)
{
...
}
C# 7.1(使用 vs 2017 更新 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:
方法一:使用项目设置窗口:
方法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>
如果您使用的是 C# 7.1 或更高版本,请使用nawfal 的答案,只需将 Main 方法的返回类型更改为Task
或Task<int>
。 如果你不是:
async Task MainAsync
。.GetAwaiter().GetResult()
来捕捉像 do0g 所说的底层异常。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.
}
还不需要这么多,但是当我使用控制台应用程序进行快速测试并需要异步时,我刚刚解决了这个问题:
class Program
{
static void Main(string[] args)
{
MainAsync(args).Wait();
}
static async Task MainAsync(string[] args)
{
// Code here
}
}
对于从 Main 异步调用任务,请使用
.NET 4.5 的 Task.Run()
Task.Factory.StartNew() 用于 .NET 4.0(可能需要 Microsoft.Bcl.Async 库用于 async 和 await 关键字)
详情:http: //blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
在 Main 中尝试将对 GetList 的调用更改为:
Task.Run(() => bs.GetList());
最新版本的 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 开始,以下签名对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);
}
当 C# 5 CTP 被引入时,你当然可以用async
标记 Main ... 尽管这样做通常不是一个好主意。 我相信这已被 VS 2013 的发布改变为一个错误。
除非您启动了任何其他前台线程,否则您的程序将在Main
完成时退出,即使它已经启动了一些后台工作。
你真正想做什么? 请注意,您的GetList()
方法目前确实不需要异步 - 它无缘无故地添加了一个额外的层。 它在逻辑上等价于(但更复杂):
public Task<List<TvChannel>> GetList()
{
return new GetPrograms().DownloadTvChannels();
}
在 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
请注意示例后面的语句:
这些示例显示异步任务在与主应用程序线程不同的线程上执行。
因此,如果您希望任务在主应用程序线程上运行,请参阅@StephenCleary的答案。
关于任务运行的线程,还要注意斯蒂芬对他的回答的评论:
您可以使用简单的
Wait
或Result
,这并没有错。 但请注意,有两个重要区别:1)所有async
延续都在线程池而不是主线程上运行,以及 2)任何异常都包装在AggregateException
中。
(有关如何合并异常处理来处理AggregateException
,请参阅异常处理(任务并行库) 。)
最后,在 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
请注意,不是将delegate
传递给Task.Run
,而是可以传递一个 lambda 函数,如下所示:
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 ...
}
为了避免在调用堆栈中某处尝试重新加入当前线程(卡在等待中)的函数时冻结,您需要执行以下操作:
class Program
{
static void Main(string[] args)
{
Bootstrapper bs = new Bootstrapper();
List<TvChannel> list = Task.Run((Func<Task<List<TvChannel>>>)bs.GetList).Result;
}
}
(演员只需要解决歧义)
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
}
}
这是假设的,但我在想:
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();
}
以下代码可用于创建主异步。 我已经调整它以使用长时间运行的任务(在此处了解更多信息: https://learn.microsoft.com/en-us/do.net/api/system.threading.tasks.taskcreationoptions?view.net-7.0 )
它还从上述响应中实施取消令牌。
private static int Main(string[] args)
{
var cts = new CancellationTokenSource();
Console.CancelKeyPress += (s, e) =>
{
e.Cancel = !cts.IsCancellationRequested;
cts.Cancel();
Console.WriteLine("CancellationRequested");
};
try
{
var task = new Task<int>(
() => MainAsync(args, cts.Token).GetAwaiter().GetResult(),
cts.Token,
TaskCreationOptions.LongRunning //https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcreationoptions?view=net-7.0
);
task.Start();
var exitCode = task.GetAwaiter().GetResult();
/*Or this.*/
//var exitCode = MainAsync(args, cts.Token).GetAwaiter().GetResult();
return exitCode;// MainAsync(args, cts.Token).GetAwaiter().GetResult();
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex);
return 1223; // Cancelled.
}
catch(Exception ex)
{
Console.WriteLine(ex);
return -1;
}
}
private static async Task<int> MainAsync(string[] args, CancellationToken cancellationToken)
{
await Something()
return;
}
在我写的下面的例子中。 您可以使用 maxDegreeOfParallelism 和 numberOfIteration 来了解/查看任务的处理方式。 学习TPL的好起点!
private static async Task<int> MainAsync(string[] args, CancellationToken cancellationToken)
{
var infos = new ConcurrentBag<Info>();
var mySuperUselessService = new BigWorkload();
int numberOfSecond = 1;
int numberOfIteration = 25; //Experiment with this
int maxDegreeOfParallelism = 4; //Experiment with this
var simulateWorkTime = TimeSpan.FromSeconds(numberOfSecond);
var informations = Enumerable.Range(1, numberOfIteration)
.Select(x => new Info() { Index = x });
var count = informations.Count();
var chunkNeeded = Math.Round(count / Convert.ToDecimal(maxDegreeOfParallelism), MidpointRounding.ToPositiveInfinity);
var splashInfo = @$"
Press CTRL + C to cancel.
Processing {count} items, maxDegreeOfParallelism set to {maxDegreeOfParallelism}.
But it will be bound by the core on the machine {Environment.ProcessorCount}.
This operation should take ~{chunkNeeded * (numberOfSecond + 0.01m)}s
And will be starting test in 2s
";
Console.WriteLine(splashInfo);
await Task.Delay(TimeSpan.FromSeconds(2));
var parralelOptions = new ParallelOptions() { MaxDegreeOfParallelism = maxDegreeOfParallelism, CancellationToken = cancellationToken};
var stopwatch = new Stopwatch();
stopwatch.Start();
var forLoopTask = Parallel.ForEachAsync(informations, parralelOptions, async (info, token) =>
{
await mySuperUselessService.Simulate(simulateWorkTime, info);
Console.WriteLine(info);
infos.Add(info);
});
await forLoopTask;
stopwatch.Stop();
foreach (var grouped in infos.GroupBy(x => x.ManagedThreadId))
{
Console.WriteLine($"ThreadId: {grouped.Key}");
foreach (var item in grouped)
{
Console.WriteLine($"\t Index: {item.Index} {item.TaskCurrentId}");
}
}
Console.WriteLine($"NumberOfThread: {infos.GroupBy(x => x.ManagedThreadId).Count()}");
Console.WriteLine($"Elasped: {stopwatch.ElapsedMilliseconds / 1000d}s");
Console.WriteLine(splashInfo);
return 0;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.