我该如何在主函数中调用异步方法?

127
public class test
{
    public async Task Go()
    {
        await PrintAnswerToLife();
        Console.WriteLine("done");
    }

    public async Task PrintAnswerToLife()
    {
        int answer = await GetAnswerToLife();
        Console.WriteLine(answer);
    }

    public async Task<int> GetAnswerToLife()
    {
        await Task.Delay(5000);
        int answer = 21 * 2;
        return answer;
    }
}

如果我想在main()方法中调用Go方法,该怎么做?我正在尝试使用C#的新功能,我知道可以将异步方法钩到事件中,通过触发该事件来调用异步方法。

但是如果我想直接在main方法中调用它,该怎么办?我应该怎么做?

我做了类似以下的事情:

class Program
{
    static void Main(string[] args)
    {
        test t = new test();
        t.Go().GetAwaiter().OnCompleted(() =>
        {
            Console.WriteLine("finished");
        });
        Console.ReadKey();
    }


}

但似乎陷入了死锁,屏幕上没有任何输出。


似乎我找到了问题所在,因为GetAwaiter().OnCompleted()会立即返回到主函数,所以当调用Console.Readkey()时,主线程被阻塞,因此来自任务的输出消息无法打印到屏幕上,因为它正在等待主线程解除阻塞。如果我用while (true)替换Console.readkey() { Thread.Sleep(1000); },那么它就可以正常工作了。 - Larry
9个回答

158

您的Main方法可以简化。对于C# 7.1及更高版本:

static async Task Main(string[] args)
{
    test t = new test();
    await t.Go();
    Console.WriteLine("finished");
    Console.ReadKey();
}

对于早期版本的C#:

static void Main(string[] args)
{
    test t = new test();
    t.Go().Wait();
    Console.WriteLine("finished");
    Console.ReadKey();
}

这是使用 async 关键字(及相关功能)的美妙之处:回调函数的使用和令人困惑的特性大大降低或消除了。

@MarcGravell 我已经将新方法纳入我的答案中。DavidG的答案提供了更多细节,但我不再过时。 - Tim S.
您可能需要修改项目文件以允许使用c# 7.0及以上版本。答案已发布在这里 - Luke
1
在早期版本的C#中,使用t.Go().GetAwaiter().GetResult()不是更好吗?这样异常就不会被包装在AggregateException中了。而且,在早期版本的C#中,这种选项不可能导致死锁吗? - Code Pope

30

不要使用 Wait(),而应该使用 new test().Go().GetAwaiter().GetResult()。这样可以避免异常被封装成聚合异常(AggregateExceptions),因此您可以像通常一样使用 try catch(Exception ex) 块来包围 Go() 方法。


不要在异步方法上调用GetResult()。这会阻塞主线程。这不仅违背了异步方法的目的,还会留下死锁的可能性。 - Ruchira
2
@Ruchira,Wait也可能发生同样的情况。那么当您没有C# 7.1或更新版本时,应该如何解决这个问题,其中main函数无法声明为async - Code Pope

29
自从C# v7.1发布以来,可以使用async 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)
{
    await DoSomethingAsync();
}

static async Task DoSomethingAsync()
{
    //...
}

2
我正在尝试使用static async Task Main(string[] args),但是我遇到了错误CS5001 Program does not contain a static 'Main' method suitable for an entry point。我已经检查了项目属性,下拉菜单中没有可用的启动对象。我正在使用最新版本的VS2017 + .NET Core 2.0。我该如何解决这个问题? - NightOwl888
@NightOwl888,你能在项目属性中看到C# 7.1吗? - DavidG
5
可以的,谢谢。默认设置是“最新主要版本”,所以默认为7.0。我将其更改为7.1,现在可以编译了。 - NightOwl888

16
class Program
{
    static void Main(string[] args)
    {
       test t = new test();
       Task.Run(async () => await t.Go());
    }
}

3
此答案创建了一个后台线程,如果前台线程先完成可能会引起问题。 - Luke

14
只要您从返回的任务中访问结果对象,就没有必要使用GetAwaiter(仅在访问结果时需要)。
static async Task<String> sayHelloAsync(){

       await Task.Delay(1000);
       return "hello world";

}

static void main(string[] args){

      var data = sayHelloAsync();
      //implicitly waits for the result and makes synchronous call. 
      //no need for Console.ReadKey()
      Console.Write(data.Result);
      //synchronous call .. same as previous one
      Console.Write(sayHelloAsync().GetAwaiter().GetResult());

}

如果您想等待任务完成并进行进一步处理:

sayHelloAsyn().GetAwaiter().OnCompleted(() => {
   Console.Write("done" );
});
Console.ReadLine();

如果你想获得 sayHelloAsync 的结果并对其进行进一步处理:

sayHelloAsync().ContinueWith(prev => {
   //prev.Result should have "hello world"
   Console.Write("done do further processing here .. here is the result from sayHelloAsync" + prev.Result);
});
Console.ReadLine();

等待函数的最后一种简单方法:

static void main(string[] args){
  sayHelloAsync().Wait();
  Console.Read();
}

static async Task sayHelloAsync(){          
  await Task.Delay(1000);
  Console.Write( "hello world");

}

这需要更多关于选择哪种方法以及为什么选择的信息。 - Joe Phillips
Wait 方法能否避免死锁? - Code Pope

5
public static void Main(string[] args)
{
    var t = new test();
    Task.Run(async () => { await t.Go();}).Wait();
}

5
在回答问题时,始终要详细说明为什么这是“答案”。 - Prasoon Karunan V

3

Use .Wait()

static void Main(string[] args){
   SomeTaskManager someTaskManager  = new SomeTaskManager();
   Task<List<String>> task = Task.Run(() => marginaleNotesGenerationTask.Execute());
   task.Wait();
   List<String> r = task.Result;
} 

public class SomeTaskManager
{
    public async Task<List<String>> Execute() {
        HttpClient client = new HttpClient();
        client.BaseAddress = new Uri("http://localhost:4000/");     
        client.DefaultRequestHeaders.Accept.Clear();           
        HttpContent httpContent = new StringContent(jsonEnvellope, Encoding.UTF8, "application/json");
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        HttpResponseMessage httpResponse = await client.PostAsync("", httpContent);
        if (httpResponse.Content != null)
        {
            string responseContent = await httpResponse.Content.ReadAsStringAsync();
            dynamic answer = JsonConvert.DeserializeObject(responseContent);
            summaries = answer[0].ToObject<List<String>>();
        }
    } 
}

1

C# 9顶级语句进一步简化了事情,现在甚至不需要做任何额外的工作来调用您的Main中的async方法,您只需这样做:

using System;
using System.Threading.Tasks;

await Task.Delay(1000);
Console.WriteLine("Hello World!");

更多信息请参见C# 9.0的新功能,顶级语句

顶级语句可以包含异步表达式。在这种情况下,合成的入口点返回一个TaskTask<int>


0
尝试使用“Result”属性。
class Program
{
    static void Main(string[] args)
    {
        test t = new test();
        t.Go().Result;
        Console.ReadKey();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接