Ne peut pas spécifier le modificateur 'async' sur la méthode 'Main' d'une application de console

je suis nouveau dans la programmation asynchrone avec le modificateur async . J'essaie de trouver comment m'assurer que ma méthode Main d'une application console fonctionne de manière asynchrone.

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

je sais que ce n'est pas asynchrone depuis le haut."Puisqu'il n'est pas possible de spécifier le modificateur async sur la méthode Main , Comment puis-je exécuter asynchrone du code dans main ?

334
demandé sur Liam 2012-02-09 14:13:37

15 réponses

comme vous l'avez découvert, dans VS11 le compilateur va rejeter une méthode async Main . Cela a été autorisé (mais jamais recommandé) dans VS2010 avec le CTP Async.

j'ai des billets de blog récents sur async/attente et programmes de console asynchrone en particulier. Voici quelques informations de fond de l'intro post:

Si "attendre" voit que le awaitable n'a pas terminé, alors il actes de manière asynchrone. Il indique à l'awaitable d'exécuter le reste de la méthode lorsqu'elle est terminée, puis retourne de la méthode async. Wait capturera également l'actuel context quand il passera le reste de la méthode à l'awaitable.

plus tard, lorsque le awaitable sera terminé, il exécutera le reste de la méthode async (dans le contexte capturé).

Voici pourquoi il s'agit d'un problème dans les programmes de Console avec un async Main :

rappelez-vous de notre intro post qu'une méthode asynchrone retournera à son interlocuteur avant qu'il ne soit terminé. Cela fonctionne parfaitement dans les applications D'UI (la méthode retourne juste à la boucle D'événement D'UI) et ASP.NET applications (la méthode renvoie le thread mais garde la requête vivante). Cela ne fonctionne pas très bien pour les programmes de la Console: Main retourne dans L'OS - donc votre programme est terminé.

une solution est de fournir votre propre contexte - une" boucle principale " pour votre programme de console qui est compatible async.

si vous avez une machine avec le CTP Async, vous pouvez utiliser GeneralThreadAffineContext de Mes Documents\Microsoft Visual Studio Async CTP\Samples(C# Testing) Unit Testing\AsyncTestUtilities . Alternativement, vous pouvez utiliser AsyncContext de mon Nito.Paquet Asyncex NuGet .

voici un exemple d'utilisation de AsyncContext ; GeneralThreadAffineContext a un usage presque identique:

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

alternativement, vous pouvez simplement bloquer le thread principal de la Console jusqu'à ce que votre travail asynchrone soit terminé:

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

notez l'utilisation de GetAwaiter().GetResult() ; cela évite l'emballage AggregateException qui se produit si vous utilisez Wait() ou Result .

mise à Jour, 2017-11-30: de Visual Studio 2017 mise à Jour 3 (15.3), la langue prend désormais en charge un async Main - tant qu'il renvoie Task ou Task<T> . Donc vous pouvez maintenant faire ceci:

class Program
{
    static async Task Main(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        var list = await bs.GetList();
    }
}

la sémantique semble être la même que le style GetAwaiter().GetResult() de blocage du fil principal. Cependant, il n'y a pas encore de spécification de langage pour C# 7.1, donc ce n'est qu'une hypothèse.

278
répondu Stephen Cleary 2017-11-30 17:53:04

vous pouvez résoudre cela avec cette construction simple:

class Program
{
    static void Main(string[] args)
    {
        Task.Run(async () =>
        {
            // Do any async anything you need here without worry
        }).GetAwaiter().GetResult();
    }
}

qui mettra tout ce que vous faites sur le ThreadPool où vous le voulez (donc les autres tâches que vous commencez/attendez ne tentent pas de rejoindre un Thread qu'ils ne devraient pas), et attendre jusqu'à ce que tout soit fait avant de fermer l'application de la Console. Pas besoin de boucles spéciales ou de libs à l'extérieur.

Edit: Intégrer Andrew solution pour les Exceptions non traitées.

311
répondu Chris Moschini 2017-01-15 16:23:41

vous pouvez faire ceci sans avoir besoin de bibliothèques externes aussi en faisant ce qui suit:

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;
    }
}
88
répondu Steven Evers 2014-01-29 23:25:02

je vais ajouter une fonctionnalité importante que toutes les autres réponses ont négligé: l'annulation.

une des grandes choses dans TPL est le soutien d'Annulation, et les applications de console ont une méthode d'annulation intégrée (CTRL+C). Il est très simple de les lier ensemble. Voici comment je structure toutes mes applications de console async:

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)
{
    ...
}
68
répondu Cory Nelson 2014-09-02 20:54:32

dans C# 7.1 vous serez en mesure de faire un async Main . Les signatures appropriées pour la méthode Main ont été étendues à:

public static Task Main();
public static Task<int> Main();
public static Task Main(string[] args);
public static Task<int> Main(string[] args);

Pour, par exemple, vous pourriez être en train de faire:

static async Task Main(string[] args)
{
    Bootstrapper bs = new Bootstrapper();
    var list = await bs.GetList();
}

au moment de la compilation, la méthode async entry point sera traduite par GetAwaitor().GetResult() .

détails: https://blogs.msdn.microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main

EDIT:

pour activer les fonctionnalités de langue C# 7.1, vous devez cliquer avec le bouton droit de la souris sur le projet et cliquer sur" Propriétés "puis aller à l'onglet" Construire". Là, cliquez sur le bouton Avancé en bas:

enter image description here

dans le menu déroulant de la version linguistique, sélectionnez " 7.1 "(ou toute valeur supérieure):

enter image description here

la valeur par défaut est" dernière version majeure " qui évaluerait (au moment de la rédaction du présent document) à C# 7.0, qui ne supporte pas async main dans les applications console.

50
répondu nawfal 2017-10-21 17:08:11

N'ont pas encore eu besoin de tant de choses, mais quand j'ai utilisé la console d'application pour des tests rapides et requis async j'ai juste résolu comme ceci:

class Program
{
    static void Main(string[] args)
    {
        MainAsync(args).Wait();
    }

    static async Task MainAsync(string[] args)
    {
        // Code here
    }
}
18
répondu Johan Falk 2014-09-02 20:36:13

C# 7.1 (utilisant vs mise à jour 2017 3) introduit async main

vous pouvez écrire:

   static async Task Main(string[] args)
  {
    await ...
  }

Pour plus de détails C# 7 de la Série, Partie 2: Async Principal

mise à jour:

vous pouvez obtenir une erreur de compilation:

programme ne contient pas de méthode "principale" statique convenant à un point d'entrée

cette erreur est due à cette vs2017.3 est configuré par défaut comme c#7.0 et non c#7.1.

vous devez modifier explicitement le paramètre de votre projet pour définir les caractéristiques c#7.1.

vous pouvez définir c#7.1 par deux méthodes:

Méthode 1: utiliser la fenêtre des paramètres du projet:

  • ouvrez les paramètres de votre projet
  • sélectionnez L'onglet Construire
  • cliquez sur le bouton Avancé
  • sélectionnez la version que vous souhaitez Comme le montre la figure suivante:

enter image description here

Method2: Modifier PropertyGroup de .csproj manuellement

ajouter cette propriété:

    <LangVersion>7.1</LangVersion>

exemple:

    <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>    
16
répondu M.Hassan 2017-09-25 17:22:09

si vous utilisez C# 7.1 ou plus tard, allez avec la réponse de nawfal et changez simplement le type de retour de votre méthode principale en Task ou Task<int> . Si vous n'êtes pas:

  • ont un async Task MainAsync comme Johan a dit .
  • , fait Appel à son .GetAwaiter().GetResult() pour attraper l'exception sous-jacente comme do0g dit .
  • annulation de la prise en charge comme Cory a dit .
  • une seconde CTRL+C devrait immédiatement mettre fin au processus. (Merci binki !)
  • Handle OperationCancelledException - retourner un code d'erreur approprié.

le code final ressemble à:

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.
}
15
répondu Şafak Gür 2018-03-01 16:59:28

De manière asynchrone tâche appelante de Main, utiliser

  1. tâche.Run () for .NET 4.5

  2. tâche.Usine.StartNew ().NET 4.0 (Peuvent exiger de Microsoft.Bcl.Async bibliothèque pour async et await mots-clés)

détails: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx

7
répondu user3408030 2014-03-11 21:25:49

dans Main try changer l'appel à GetList en:

Task.Run(() => bs.GetList());
4
répondu mysticdotnet 2014-02-07 14:19:01

lorsque le CTP C# 5 a été introduit, vous avez certainement pourrait marquer Main avec async ... bien qu'il n'était généralement pas une bonne idée de le faire. Je crois que cela a été modifié par la publication de VS 2013 pour devenir une erreur.

sauf si vous avez commencé un autre premier plan threads, votre programme se terminera lorsque Main sera terminé, même s'il a commencé un travail de fond.

Qu'est-ce que vous vraiment essaie de faire? Notez que votre méthode GetList() n'a vraiment pas besoin d'être async pour le moment - elle ajoute une couche supplémentaire sans raison réelle. C'est logiquement équivalent à (mais plus compliqué que):

public Task<List<TvChannel>> GetList()
{
    return new GetPrograms().DownloadTvChannels();
}
4
répondu Jon Skeet 2016-11-21 08:50:35

la plus récente version de C# - C # 7.1 permet de créer une application de console async. Pour activer C # 7.1 dans project, vous devez mettre à jour votre VS à au moins 15.3, et changer la version C# en C# 7.1 ou C# latest minor version . Pour ce faire, allez à Propriétés du projet -> Build -> Advanced -> Language version.

après cela, le code suivant fonctionnera:

internal class Program
{
    public static async Task Main(string[] args)
    {
         (...)
    }
4
répondu Kedrzu 2017-08-22 12:01:34

sur MSDN, la documentation pour la tâche .Méthode Run (Action) fournit cet exemple qui montre comment exécuter une méthode asynchrone de 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

Notez cette Déclaration qui suit l'exemple:

les exemples montrent que la tâche asynchrone s'exécute sur une thread que le thread d'application principal.

donc, si à la place vous voulez que la tâche s'exécute sur le fil d'application principal, voir la réponse par @StephenCleary .

et en ce qui concerne le fil sur lequel la tâche s'exécute, noter également de Stephen "commentaire sur sa réponse:

vous can utilisez un simple Wait ou Result , et il n'y a rien de mal avec que. Mais sachez qu'il y a deux des différences importantes: 1) toutes les continuations async s'exécutent sur le pool de thread plutôt que sur la main fil, et 2) Toutes les exceptions sont enveloppées dans un AggregateException .

(Voir la gestion des exceptions (Task Parallel Library) pour savoir comment intégrer la gestion des exceptions pour faire face à une AggregateException .)


enfin, sur MSDN à partir de la documentation pour la tâche .Méthode Délai (Timeespan) , cet exemple montre comment exécuter une tâche asynchrone qui renvoie une valeur:

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

notez qu'au lieu de passer un delegate à Task.Run , vous pouvez passer une fonction lambda comme ceci:

var t = Task.Run(async () =>
        {
            await Task.Delay(TimeSpan.FromSeconds(1.5));
            return 42;
        });
3
répondu DavidRR 2017-05-23 12:34:53

pour éviter le gel lorsque vous appelez une fonction quelque part en bas de la pile d'appels qui tente de rejoindre le fil courant (qui est coincé dans une attente), vous devez faire ce qui suit:

class Program
{
    static void Main(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        List<TvChannel> list = Task.Run((Func<Task<List<TvChannel>>>)bs.GetList).Result;
    }
}

(le casting est seulement nécessaire pour résoudre l'ambiguïté)

1
répondu Nathan Phillips 2015-04-07 14:25:28

dans mon cas, j'avais une liste d'emplois que je voulais exécuter en asynchrone à partir de ma méthode principale, j'ai utilisé cela dans la production depuis un certain temps et fonctionne très bien.

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 ...
}
1
répondu user_v 2016-07-26 05:34:32