我对使用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中异步运行代码?


当前回答

如果你使用的是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.
}

其他回答

你也可以在不需要外部库的情况下做到这一点,方法如下:

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;
    }
}

在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的回答。

关于任务运行的线程,也请注意Stephen对他的回答的评论:

您可以使用简单的Wait或Result,没有任何问题 的。但要注意两者之间有两个重要的区别: 所有异步延续都运行在线程池上,而不是主线程池上 2)任何异常都包装在AggregateException中。

(参见异常处理(任务并行库)了解如何合并异常处理来处理AggregateException。)


最后,从MSDN上的任务文档。延迟方法(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

注意,不是传递一个委托给Task。运行时,你可以像这样传递一个lambda函数:

var t = Task.Run(async () =>
        {
            await Task.Delay(TimeSpan.FromSeconds(1.5));
            return 42;
        });

下面的代码可以用来创建一个主异步。我已经调整它使用长时间运行的任务(了解更多信息在这里:https://learn.microsoft.com/en-us/dotnet/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;
        
    }

如果你使用的是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.
}

对于从Main异步调用任务,使用

Task.Run()用于。net 4.5 Task.Factory.StartNew() for .NET 4.0(可能需要Microsoft.Bcl.Async库用于async和await关键字)

细节: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx