Der Modifizierer ‘async’ für die ‘Main’-Methode einer Konsolenanwendung kann nicht angegeben werden

Ich bin neu in der asynchronen Programmierung mit dem async Modifikator. Ich versuche herauszufinden, wie ich sicherstellen kann, dass meine Main Methode einer Konsolenanwendung tatsächlich asynchron ausgeführt wird.

 class Program { static void Main(string[] args) { Bootstrapper bs = new Bootstrapper(); var list = bs.GetList(); } } public class Bootstrapper { public async Task<List> GetList() { GetPrograms pro = new GetPrograms(); return await pro.DownloadTvChannels(); } } 

Ich weiß, das läuft nicht asynchron von “oben”. Da es nicht möglich ist, den async Modifikator für die Main Methode anzugeben, wie kann ich Code innerhalb der main asynchron ausführen?

Wie Sie festgestellt haben, wird der Compiler in VS11 eine async Main Methode nicht zulassen. Dies wurde in VS2010 mit dem Async CTP erlaubt (aber nie empfohlen).

Ich habe kürzlich Blogposts über asynchrone / erwarten und asynchrone Konsolenprogramme im Besonderen. Hier einige Hintergrundinformationen aus dem Intro-Post:

Wenn “await” sieht, dass das Erwartete nicht abgeschlossen ist, handelt es asynchron. Es teilt dem Erwarteten mit, dass der Rest der Methode nach Abschluss ausgeführt werden soll, und kehrt dann von der Async-Methode zurück. Warten wird auch den aktuellen Kontext erfassen, wenn es den Rest der Methode auf das Erwartete übergibt.

Später, wenn das Erwartete abgeschlossen ist, wird der Rest der Async-Methode (im erfassten Kontext) ausgeführt.

Hier ist, warum dies ein Problem in Console-Programmen mit einem async Main :

Denken Sie daran, dass eine asynchrone Methode in unserem Intro-Post zu ihrem Aufrufer zurückkehrt, bevor sie abgeschlossen ist. Dies funktioniert perfekt in UI-Anwendungen (die Methode kehrt nur zur UI-Ereignisschleife zurück) und in ASP.NET-Anwendungen (die Methode gibt den Thread zurück, hält die Anforderung jedoch am Leben). Bei Konsolenprogrammen funktioniert das nicht so gut: Main kehrt zum Betriebssystem zurück – Ihr Programm wird also beendet.

Eine Lösung besteht darin, Ihren eigenen Kontext bereitzustellen – eine “Hauptschleife” für Ihr Konsolenprogramm, die async-kompatibel ist.

Wenn Sie über einen Computer mit dem Async-CTP verfügen, können Sie GeneralThreadAffineContext aus Eigene Dateien \ Microsoft Visual Studio Async CTP \ Samples (C # Testing) Unit Testing \ AsyncTestUtilities verwenden . Alternativ können Sie AsyncContext aus meinem Nito.AsyncEx NuGet-Paket verwenden .

Hier ist ein Beispiel mit AsyncContext ; GeneralThreadAffineContext hat fast identische Verwendung:

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

Alternativ können Sie den Hauptkonsolenthread einfach blockieren, bis Ihre asynchrone Arbeit abgeschlossen ist:

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

Beachten Sie die Verwendung von GetAwaiter().GetResult() ; Dies verhindert das AggregateException Wrapping, das AggregateException , wenn Sie Wait() oder Result .

Update, 2017-11-30: Ab Visual Studio 2017 Update 3 (15.3) unterstützt die Sprache nun einen asynchronen Hauptteil – so lange sie Task oder Task zurückgibt. Sie können dies jetzt tun:

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

Die Semantik scheint mit der GetAwaiter().GetResult() des Blockierens des Hauptthreads GetAwaiter().GetResult() . Es gibt jedoch noch keine Sprachspezifikation für C # 7.1, daher ist dies nur eine Annahme.

Sie können dies mit diesem einfachen Konstrukt lösen:

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

Das bringt alles, was Sie tun, auf den ThreadPool, wo Sie es haben möchten (damit andere Tasks, die Sie starten / warten, nicht versuchen, einem Thread beizutreten, was sie nicht tun sollten) und warten Sie, bis alles fertig ist, bevor Sie die Konsolen-App schließen. Keine Notwendigkeit für spezielle Schleifen oder außerhalb der Bibliotheken.

Bearbeiten: Integrieren Sie Andrew’s Lösung für nicht erfasste Ausnahmen.

Sie können dies auch ohne externe Bibliotheken tun, indem Sie Folgendes tun:

 class Program { static void Main(string[] args) { Bootstrapper bs = new Bootstrapper(); var getListTask = bs.GetList(); // returns the Task> Task.WaitAll(getListTask); // block while the task completes var list = getListTask.Result; } } 

Ich füge ein wichtiges Merkmal hinzu, das alle anderen Antworten übersehen haben: die Stornierung.

Eines der großen Dinge in TPL ist die Unterstützung von Stornierungen, und in Konsolen-Apps ist eine Methode zum Löschen integriert (STRG + C). Es ist sehr einfach, sie zusammen zu binden. So strukturiere ich alle meine asynchronen Konsolen-Apps:

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

In C # 7.1 können Sie einen ordentlichen asynchronen Hauptordner erstellen. Die entsprechenden Signaturen für die Main Methode wurden erweitert auf:

 public static Task Main(); public static Task Main(); public static Task Main(string[] args); public static Task Main(string[] args); 

Zum Beispiel könnten Sie tun:

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

Zur Kompilierungszeit wird die asynchrone Einstiegspunktmethode übersetzt, um GetAwaitor().GetResult() .

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

BEARBEITEN:

Um C # 7.1-Sprachfunktionen zu aktivieren, müssen Sie mit der rechten Maustaste auf das Projekt klicken und auf “Eigenschaften” klicken und dann auf die Registerkarte “Erstellen” gehen. Klicken Sie dort auf die erweiterte Schaltfläche:

Bildbeschreibung hier eingeben

Wählen Sie im Dropdown-Menü für die Sprachversion “7.1” (oder einen höheren Wert):

Bildbeschreibung hier eingeben

Der Standard ist “neueste Hauptversion”, die (zum Zeitpunkt des Schreibens) C # 7.0 auswerten würde, die asynchrone Hauptfunktionen in Konsolenanwendungen nicht unterstützt.

Hab noch nicht so viel gebraucht, aber wenn ich Konsolen-Anwendung für Quick-Tests und Async benötigt habe, habe ich es gerade so getriggers:

 class Program { static void Main(string[] args) { MainAsync(args).Wait(); } static async Task MainAsync(string[] args) { // Code here } } 

C # 7.1 (mit vs 2017 Update 3) führt asynchrone Hauptfunktionen ein

Du kannst schreiben:

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

Für weitere Details C # 7 Series, Teil 2: Async Main

Aktualisieren:

Möglicherweise erhalten Sie einen Kompilierungserrors:

Das Programm enthält keine statische Hauptmethode, die für einen Einstiegspunkt geeignet ist

Dieser Fehler ist darauf zurückzuführen, dass vs2017.3 standardmäßig als c # 7.0 und nicht als c # 7.1 konfiguriert ist.

Sie sollten die Einstellung Ihres Projekts explizit ändern, um c # 7.1-functionen festzulegen.

Sie können c # 7.1 auf zwei Arten einstellen:

Methode 1: Verwenden des Fensters mit den Projekteinstellungen:

  • Öffnen Sie die Einstellungen Ihres Projekts
  • Wählen Sie die Registerkarte Erstellen
  • Klicken Sie auf die Schaltfläche Erweitert
  • Wählen Sie die gewünschte Version Wie in der folgenden Abbildung gezeigt:

Bildbeschreibung hier eingeben

Methode 2: Ändern Sie PropertyGroup von .csproj manuell

Fügen Sie diese Eigenschaft hinzu:

  7.1 

Beispiel:

   AnyCPU true full false bin\Debug\ DEBUG;TRACE prompt 4 false 7.1  

Wenn Sie C # 7.1 oder höher verwenden, verwenden Sie die Antwort von nawfal und ändern Sie einfach den Rückgabetyp Ihrer Main-Methode in Task oder Task . Wenn Sie nicht sind:

  • Habe eine async Task MainAsync wie Johan gesagt hat .
  • Rufen Sie seine .GetAwaiter().GetResult() , um die zugrunde liegende Ausnahme abzufangen, wie do0g sagte .
  • Support-Absage wie Cory sagte .
  • Ein zweites CTRL+C sollte den process sofort beenden. (Danke Binki !)
  • Handle OperationCancelledException – gibt einen entsprechenden Fehlercode zurück.

Der endgültige Code sieht folgendermaßen aus:

 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 MainAsync(string[] args, CancellationToken cancellationToken) { // Your code... return await Task.FromResult(0); // Success. } 

Verwenden Sie für den asynchronen Aufruf von Task aus Main

  1. Task.Run () für .NET 4.5

  2. Task.Factory.StartNew () für .NET 4.0 (erfordert möglicherweise Microsoft.BCl.Async-Bibliothek für Async und erwarten Schlüsselwörter)

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

Versuchen Sie in Main, den Aufruf von GetList zu ändern:

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

Als das C # 5 CTP eingeführt wurde, konnte man Main mit async markieren … obwohl das im Allgemeinen keine gute Idee war. Ich glaube, dass dies durch die Veröffentlichung von VS 2013 zu einem Fehler wurde.

Sofern Sie keine anderen Vordergrund- Threads gestartet haben, wird Ihr Programm beendet, wenn Main abgeschlossen ist, auch wenn es im Hintergrund gestartet wurde.

Was versuchst du wirklich ? Beachten Sie, dass Ihre GetList() -Methode im Moment nicht unbedingt GetList() muss – sie fügt einen zusätzlichen Layer ohne wirklichen Grund hinzu. Es ist logisch äquivalent zu (aber komplizierter als):

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

In MSDN enthält die Dokumentation für Task.Run-Methode (Action) dieses Beispiel, das zeigt, wie eine Methode asynchron von 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 

Beachten Sie diese statement, die dem Beispiel folgt:

Die Beispiele zeigen, dass die asynchrone Task in einem anderen Thread als der Hauptanwendungs-Thread ausgeführt wird.

Wenn die Aufgabe stattdessen im Hauptanwendungs-Thread ausgeführt werden soll, sehen Sie sich die Antwort von @StephenCleary an .

Und bezüglich des Threads, auf dem die Aufgabe ausgeführt wird, beachte auch Stephens Kommentar zu seiner Antwort:

Sie können ein einfaches Wait oder ein Result , und daran ist nichts falsch. Beachten Sie jedoch, dass es zwei wichtige Unterschiede gibt: 1) Alle async Fortsetzungen werden im Thread-Pool und nicht im Hauptthread ausgeführt, und 2) alle Ausnahmen werden in eine AggregateException .

(Siehe Ausnahmebehandlung (Parallele Task-Bibliothek) für die Integration der Ausnahmebehandlung zur Behandlung einer AggregateException .)


Schließlich zeigt dieses Beispiel in MSDN aus der Dokumentation für Task.Delay-Methode (TimeSpan) , wie eine asynchrone Task ausgeführt wird, die einen Wert zurückgibt:

 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 

Beachten Sie, dass Sie anstatt einen delegate an Task.Run können, stattdessen eine Lambda-function wie Task.Run können:

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

Die neueste Version von C # – C # 7.1 ermöglicht das Erstellen einer asynchronen Konsolen-App. Um C # 7.1 im Projekt zu aktivieren, müssen Sie Ihren VS auf mindestens 15.3 aktualisieren und die C # -Version in C# 7.1 oder C# latest minor version ändern. Wechseln Sie dazu zu Projekteigenschaften -> Erstellen -> Erweitert -> Sprachversion.

Danach funktioniert folgender Code:

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

Um ein Einfrieren zu vermeiden, wenn Sie eine function irgendwo im Call-Stack aufrufen, die versucht, den aktuellen Thread (der in einem Wartezustand festsitzt) erneut zu verbinden, müssen Sie Folgendes tun:

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

(Die Besetzung ist nur erforderlich, um Mehrdeutigkeit aufzulösen)

In meinem Fall hatte ich eine Liste von Jobs, die ich in async von meiner Hauptmethode ausführen wollte, habe das in der Produktion seit einiger Zeit verwendet und funktioniert gut.

 static void Main(string[] args) { Task.Run(async () => { await Task.WhenAll(jobslist.Select(nl => RunMulti(nl))); }).GetAwaiter().GetResult(); } private static async Task RunMulti(List joblist) { await ... }