web-dev-qa-db-fra.com

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

Je suis novice en programmation asynchrone avec le modificateur async. J'essaie de comprendre comment m'assurer que ma méthode Main d'une application console s'exécute réellement 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 cela ne fonctionne pas de manière 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 du code dans main de manière asynchrone?

398
danielovich

Comme vous l'avez découvert, dans VS11, le compilateur interdisera l'utilisation d'une méthode async Main. Ceci était autorisé (mais jamais recommandé) dans VS2010 avec le CTP Async.

J'ai récemment publié des articles sur async/wait et programmes de console asynchrones en particulier. Voici quelques informations de base du post d’intro:

Si "wait" voit que l'attente n'est pas terminée, il agit de manière asynchrone. Il indique à l'attendu d'exécuter le reste de la méthode à la fin, puis renvoieà partir de la méthode asynchrone. Await capturera également le contexte actuel lorsqu'il passera le reste de la méthode à l'attente.

Plus tard, lorsque l'attente sera terminée, il exécutera le reste de la méthode asynchrone (dans le contexte capturé).

Voici pourquoi cela pose un problème dans les programmes de console avec un async Main:

Rappelez-vous de notre post d’introduction qu’une méthode asynchrone renverraà son appelant avant la fin de celle-ci. Cela fonctionne parfaitement dans les applications d'interface utilisateur (la méthode ne fait que retourner à la boucle d'événements d'interface utilisateur) et dans les applications ASP.NET (la méthode renvoie le thread mais conserve la requête active). Cela ne fonctionne pas très bien pour les programmes de la console: Main retourne au système d’exploitation - votre programme se ferme.

Une solution consiste à fournir votre propre contexte - une "boucle principale" pour votre programme de console compatible asynchrone.

Si vous avez une machine avec le CTP asynchrone, vous pouvez utiliser GeneralThreadAffineContext à partir de Mes documents\Microsoft Visual Studio CTP asynchrone\Samples (test C #) Testing de l'unité\AsyncTestUtilities. Vous pouvez également utiliser AsyncContext à partir de mon paquet Nito.AsyncEx NuGet .

Voici un exemple utilisant AsyncContext; GeneralThreadAffineContext a une utilisation 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();
    }
}

Vous pouvez également bloquer le thread principal de la console jusqu'à la fin de votre travail asynchrone:

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 le wrapping AggregateException qui se produit si vous utilisez Wait() ou Result.

Update, 2017-11-30: À partir de la mise à jour 3 de Visual Studio 2017 (15.3), le langage prend désormais en charge un async Main - aussi longtemps car il retourne 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 chose que le style GetAwaiter().GetResult() de blocage du thread principal. Cependant, il n'y a pas encore de spécification de langage pour C # 7.1, ce n'est donc qu'une hypothèse.

343
Stephen Cleary

Vous pouvez résoudre ceci 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();
    }
}

Cela mettra tout ce que vous faites sur le ThreadPool où vous le souhaitez (ainsi, les autres tâches que vous démarrez/attendez ne tentez pas de rejoindre un Thread comme elles ne devraient pas) et attendront que tout soit terminé avant de fermer l'application Console. Pas besoin de boucles spéciales ou de bibliothèques extérieures.

Edit: Incorporer la solution d'Andrew pour les exceptions non capturées.

344
Chris Moschini

Vous pouvez le faire sans avoir besoin de bibliothèques externes en procédant comme 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;
    }
}
90
Steven Evers

J'ajouterai une caractéristique importante que toutes les autres réponses ont négligée: l'annulation.

L’un des principaux avantages de TPL est la prise en charge de l’annulation. Les applications pour console ont une méthode d’annulation intégrée (CTRL + C). C'est très simple de les lier ensemble. Voici comment je structure toutes mes applications de console asynchrones:

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)
{
    ...
}
74
Cory Nelson

En C # 7.1, vous serez capable de faire un Asynchrone approprié . 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);

Par exemple vous pourriez faire:

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

Au moment de la compilation, la méthode du point d'entrée asynchrone sera traduite en appelant GetAwaitor().GetResult().

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

MODIFIER:

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

enter image description here

Dans le menu déroulant Version de la langue, sélectionnez "7.1" (ou toute valeur supérieure):

enter image description here

La valeur par défaut est "dernière version majeure" qui serait évaluée (au moment de la rédaction de cet article) en C # 7.0, qui ne prend pas en charge l'async principal dans les applications de la console.

70
nawfal

Je n'ai pas eu besoin de cela pour l'instant, mais quand j'ai utilisé l'application console pour les tests rapides et requis async, je l'ai juste résolue comme ceci:

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

    static async Task MainAsync(string[] args)
    {
        // Code here
    }
}
19
Johan Falk

C # 7.1 (en utilisant vs vs 2017 update 3) introduit async principal

Tu peux écrire:

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

Pour plus de détails C # 7 Series, Part 2: Async Main

Mise à jour:

Vous pouvez obtenir une erreur de compilation:

Le programme ne contient pas de méthode 'Main' statique adaptée à un point d'entrée

Cette erreur est due au fait que vs2017.3 est configuré par défaut en tant que c # 7.0 et non c # 7.1.

Vous devez explicitement modifier les paramètres de votre projet pour définir les fonctionnalités de c # 7.1.

Vous pouvez définir c # 7.1 de deux manières:

Méthode 1: Utilisation de la fenêtre de configuration du projet:

  • Ouvrez les paramètres de votre projet
  • Sélectionnez l'onglet Construire
  • Cliquez sur le bouton Avancé
  • Sélectionnez la version de votre choix, comme illustré dans la figure suivante:

enter image description here

Méthode 2: Modifier le groupe de propriétés 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>    
18
M.Hassan

Si vous utilisez C # 7.1 ou une version ultérieure, utilisez le réponse de nawfal et changez simplement le type de retour de votre méthode Main en Task ou Task<int>. Si tu n'es pas:

  • Avoir un async Task MainAsynccomme Johan l'a dit .
  • Appelez sa .GetAwaiter().GetResult() pour intercepter l'exception sous-jacente comme dit do0g .
  • Annulation du support comme Cory a dit .
  • Un second CTRL+C devrait terminer le processus immédiatement. (Merci binki !)
  • Handle OperationCancelledException - renvoie un code d'erreur approprié.

Le code final ressemble à ceci:

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.
}
17
Şafak Gür

Pour appeler de manière asynchrone une tâche depuis Main, utilisez

  1. Task.Run () pour .NET 4.5

  2. Task.Factory.StartNew () for .NET 4.0 (Peut nécessiter la bibliothèque Microsoft.Bcl.Async pour les mots clés async et wait)

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

7
user3408030

La dernière version de C # - C # 7.1 permet de créer une application console asynchrone. Pour activer le projet C # 7.1 dans un projet, vous devez mettre à niveau votre VS à une version minimale de 15.3 et modifier la version de C # en C# 7.1 ou C# latest minor version. Pour ce faire, sélectionnez Propriétés du projet -> Construire -> Avancé -> Version du langage.

Après cela, le code suivant fonctionnera:

internal class Program
{
    public static async Task Main(string[] args)
    {
         (...)
    }
4
Kedrzu

Lorsque le CTP C # 5 a été introduit, vous avez certainement vous pouvez marquer Main avec async... bien que ce n’était généralement pas une bonne idée de le faire. Je pense que cela a été modifié par la publication de VS 2013 pour devenir une erreur.

À moins que vous n'ayez démarré d'autres threads au premier plan, votre programme se fermera à la fin de Main, même s'il a démarré un travail en arrière-plan.

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

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

Dans Main, essayez de remplacer l'appel à GetList par:

Task.Run(() => bs.GetList());
4
mysticdotnet

Sur MSDN, la documentation de Méthode Task.Run (Action) fournit cet exemple qui montre comment exécuter une méthode de manière asynchrone à partir 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 un thread différent de celui de l'application principale.

Ainsi, si vous préférez que la tâche soit exécutée sur le thread principal de l’application, voir la réponse by @ StephenCleary .

Et en ce qui concerne le fil sur lequel la tâche est exécutée, notez également le commentaire commentaire de Stephen sur sa réponse:

Vous pouvez utiliser un simple Wait ou Result, et il n’ya rien de mal à cela. Sachez toutefois qu'il existe deux différences importantes: 1) toutes les continuations async sont exécutées sur le pool de threads plutôt que sur le thread principal, et 2) toutes les exceptions sont encapsulées dans un AggregateException.

(Voir Gestion des exceptions (bibliothèque parallèle de tâches) pour savoir comment incorporer la gestion des exceptions afin de traiter un AggregateException.)


Enfin, sous MSDN, dans la documentation de Méthode Task.Delay (TimeSpan) , cet exemple montre comment exécuter une tâche asynchrone renvoyant 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 plutôt passer une fonction lambda comme ceci:

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

Pour éviter le gel lorsque vous appelez une fonction quelque part dans la pile d'appels qui tente de rejoindre le thread en cours (qui est bloquée dans une attente), vous devez procéder comme 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
Nathan Phillips

Dans mon cas, j'avais une liste de tâches que je voulais exécuter en async à partir de ma méthode principale, je l'utilisais depuis un certain temps en production et cela fonctionnait 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
user_v