我对使用async修饰符进行异步编程并不熟悉。我试图弄清楚如何确保我的控制台应用程序的主要方法实际上是异步运行的。

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方法上指定异步修饰符,我如何在Main中异步运行代码?


当前回答

在我的情况下,我有一个工作列表,我想从我的主方法异步运行,已经在生产中使用了相当长一段时间,工作良好。

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)
    {
        MainAsync(args).Wait();
    }

    static async Task MainAsync(string[] args)
    {
        // Code here
    }
}
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# 5 CTP被引入时,你当然可以用async…尽管这样做通常不是个好主意。我相信这是在VS 2013的发布中改变的,成为一个错误。

除非你已经启动了任何其他前台线程,否则你的程序将在Main完成时退出,即使它已经启动了一些后台工作。

你到底想做什么?请注意,你的GetList()方法目前真的不需要是异步的——它添加了一个额外的层,没有真正的原因。它在逻辑上等价于(但比):

public Task<List<TvChannel>> GetList()
{
    return new GetPrograms().DownloadTvChannels();
}

如果你使用的是c# 7.1或更高版本,使用nawfal的答案,只需将Main方法的返回类型更改为Task或Task<int>。如果你不是:

有一个像Johan说的异步任务MainAsync。 调用它的. getawaiter (). getresult()来捕获像do0g所说的底层异常。 像科里说的那样取消支持。 第二个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.
}

正如你所发现的,在VS11中编译器将不允许async Main方法。在VS2010的Async CTP中,这是允许的(但从不推荐)。

更新,2017-11-30:从Visual Studio 2017更新3(15.3)开始,该语言现在支持异步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还没有语言规范,所以这只是一个假设。


我最近有一篇关于异步/等待和异步控制台程序的博客文章。以下是介绍帖子中的一些背景信息:

如果“await”看到可等待对象还没有完成,那么它就会异步操作。它告诉可等待对象在方法完成时运行该方法的剩余部分,然后从异步方法返回。当Await将方法的剩余部分传递给可等待对象时,它还将捕获当前上下文。 稍后,当可等待对象完成时,它将执行异步方法的剩余部分(在捕获的上下文中)。

以下是为什么这在带有异步Main的控制台程序中是一个问题:

请记住,在我们的介绍文章中,异步方法将在完成之前返回给它的调用者。这在UI应用程序(方法只返回到UI事件循环)和ASP。NET应用程序(该方法返回线程,但保持请求活动)。这对于控制台程序来说就不太适用了:Main返回到操作系统——所以你的程序退出了。

一个解决方案是提供你自己的上下文——一个异步兼容的控制台程序的“主循环”。

如果你有一台带有Async CTP的机器,你可以使用My Documents\Microsoft Visual Studio Async CTP\Samples(c# Testing) Unit Testing\AsyncTestUtilities中的GeneralThreadAffineContext。或者,你可以从我的Nito使用AsyncContext。AsyncEx NuGet包。

下面是一个使用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包装。