我是使用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
内异步运行代码?
答案 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/await和asynchronous 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()
;如果您使用AggregateException
或Wait()
,这可以避免Result
包裹。
更新,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还没有语言规范,所以这只是一个假设。
答案 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;标签。在那里,单击底部的高级按钮:
从语言版本下拉菜单中选择&#34; 7.1&#34; (或任何更高的价值):
默认为&#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:使用项目设置窗口:
方法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,然后将主要方法的返回类型更改为Task
或Task<int>
。如果你不是:
async Task MainAsync
like Johan said。.GetAwaiter().GetResult()
来捕获基础异常like do0g 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异步调用任务,请使用
.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
答案 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.1
或C# 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回答:
你可以使用简单的
中Wait
或Result
,而且没有错 接着就,随即。但请注意,有两个重要的区别: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
}
}