在C#控制台应用程序中使用“async”
在C#控制台应用程序中使用“async”
我有这个简单的代码:
public static async TaskSumTwoOperationsAsync() { var firstTask = GetOperationOneAsync(); var secondTask = GetOperationTwoAsync(); return await firstTask + await secondTask; } private async Task GetOperationOneAsync() { await Task.Delay(500); // 仅仅是为了模拟一个耗时的操作 return 10; } private async Task GetOperationTwoAsync() { await Task.Delay(100); // 仅仅是为了模拟一个耗时的操作 return 5; }
很好。这个代码编译通过。
但是假设我有一个控制台应用程序,并且我想运行上面的代码(调用`SumTwoOperationsAsync()`)。
static void Main(string[] args) { SumTwoOperationsAsync(); }
但是我读到了(当使用`sync`时)我必须在上下两个方向上同步:
这是否意味着我的`Main`函数应该标记为`async`?
嗯,它不行,因为会有编译错误:
一个入口点不能标记为“async”修饰符
如果我理解异步的东西,线程将进入`Main`函数 → `SumTwoOperationsAsync` → 调用两个函数并退出。但在`SumTwoOperationsAsync`之前
我错过了什么吗?
在C#的控制台应用程序中使用"async"关键字的原因是希望在主线程中执行异步操作。然而,使用"async"关键字时会出现问题,因为控制台应用程序的入口点是Main方法,而Main方法不能是异步的。
为了解决这个问题,可以通过创建一个辅助方法MainAsync来实现异步操作,并在Main方法中调用该方法。具体的做法是将Main方法修改为静态的,并在其中创建一个Task对象t,将MainAsync方法作为参数传递给该对象。然后,使用t.Wait()方法来等待异步操作的完成。
下面是实现的代码示例:
static void Main(string[] args) { Task t = MainAsync(args); t.Wait(); } static async Task MainAsync(string[] args) { // 执行异步操作的代码 }
通过这种方式,我们可以在控制台应用程序中使用"async"关键字,并在主线程中执行异步操作。这样就可以避免由于Main方法不能是异步的而导致的问题。
在大多数项目类型中,您的async“上”和“下”将在async void事件处理程序结束或返回一个Task到您的框架中。
然而,控制台应用程序不支持这样做。
您可以在返回的任务上直接使用Wait:
static void Main()
{
MainAsync().Wait();
// or, if you want to avoid exceptions being wrapped into AggregateException:
// MainAsync().GetAwaiter().GetResult();
}
static async Task MainAsync()
{
...
}
或者您可以使用自己的上下文,例如我写的这个:
static void Main()
{
AsyncContext.Run(() => MainAsync());
}
static async Task MainAsync()
{
...
}
更多关于async控制台应用程序的信息可以在我的博客上找到。
如果不是控制台应用程序,那么我的观察结果是正确的吗?线程在SumTwoOperationsAsync停止了...对吗?(假设主调用方法没有标记为async)
我在我的博客上有一篇介绍如何使用async的文章,通过"上下文"来解释线程是如何工作的。
重新阅读您的答案-您是说在控制台应用程序中使用async是没有意义的吗?
:不是。虽然不太常见,但除非您正在使用仅支持async的API,否则这是没什么问题的。控制台(和其他桌面)应用程序通常不太关心浪费线程的问题。
如果我不想添加第二个MainAsync方法,那么这与Task.Run( async () => { ... async calls ... } ).Wait();一样吗?
:您可以使用Task.Run(..).GetAwaiter().GetResult();,它将阻塞主线程直到异步工作完成。AsyncContext.Run是不同的;它在调用线程上运行一个“主循环”,并在单线程上执行所有异步工作。
第一种方法似乎不能很好地处理异常。抛出的异常会错过周围的try/catch块。至少在我的应用程序中会崩溃,原因不明...
:不,它保证会在catch中捕获异常。如果您没有看到这种情况,请提出自己的问题,并提供一个最小的代码示例。
我的错。异常被捕获了,只是我的异常处理代码抛出了新的异常。
值得一提的是,处理根跳板上的异常的另一种选择是AppDomain.CurrentDomain.UnhandledException += (o, e) => { /* ... */ };。
如果Stephen的评论不太清楚(我花了一点时间才明白...),区别在于,虽然Task.Run(..).GetAwaiter().GetResult()会从任务池中分配一个新的任务(可能会启动新线程或其他资源)来运行应用程序,然后讽刺的是,它会浪费当前执行的线程来阻塞刚刚创建的任务,而Stephen的AsyncContext更有效地利用了您隐含提供的线程-即您当前在Main中执行的线程-来完成这个任务。
MainAsync().GetAwaiter().GetResult();您太棒了!