Nie można określić modyfikatora "async" w metodzie "Main" aplikacji konsolowej

Jestem nowy w programowaniu asynchronicznym z modyfikatorem async. Próbuję dowiedzieć się, jak upewnić się, że moja metoda Main aplikacji konsolowej działa asynchronicznie.

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

Wiem, że to nie działa asynchronicznie od " góry."Ponieważ nie można określić modyfikatora async w metodzie Main, Jak mogę uruchomić kod w main asynchronicznie?

Author: Liam, 2012-02-09

16 answers

Jak odkryłeś, w VS11 kompilator zablokuje metodę async Main. Było to dozwolone (ale nigdy nie zalecane) w VS2010 z asynchronicznym CTP.

Mam ostatnio na blogu posty o asynchronicznych/oczekujących i asynchronicznych programach konsolowych w szczególności. Oto kilka informacji z postu intro:

Jeśli "wait" widzi, że awaitable nie zostało zakończone, to działa asynchronicznie. Informuje o uruchomieniu pozostałej części metody, gdy kończy, a następnie zwraca Z metody async. Waiting przechwytuje również bieżący kontekst , gdy przekazuje pozostałą część metody do awaitable.

Później, gdy awaitable się zakończy, wykona resztę metody asynchronicznej (w przechwyconym kontekście).

Oto dlaczego jest to problem w programach konsolowych z async Main:

Pamiętaj z naszego wpisu intro, że metoda asynchroniczna zwróci do swojej / align = "left" / Działa to doskonale w aplikacjach UI (metoda po prostu wraca do pętli zdarzeń UI) i ASP.NET aplikacje (metoda zwraca poza wątkiem, ale utrzymuje żądanie przy życiu). To nie działa tak dobrze dla programów konsolowych: Main wraca do systemu operacyjnego - więc twój program kończy pracę.

Jednym z rozwiązań jest zapewnienie własnego kontekstu - "pętli głównej" dla programu konsoli, który jest zgodny z asynchronią.

Jeśli masz maszynę z asynchronicznym CTP, możesz użyj GeneralThreadAffineContext z My Documents\Microsoft Visual Studio Async CTP\Samples (C # Testing) Unit Testing\Asynctestu. Alternatywnie można użyć AsyncContext z mojego Nito.Pakiet AsyncEx NuGet .

Oto przykład użycia AsyncContext; GeneralThreadAffineContext ma prawie identyczne zastosowanie:

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

Alternatywnie, możesz po prostu zablokować główny wątek konsoli do czasu zakończenia pracy asynchronicznej:

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

Zwróć uwagę na użycie GetAwaiter().GetResult(); pozwala to uniknąć AggregateException owijania, które dzieje się, jeśli używasz Wait() lub Result.

Aktualizacja, 2017-11-30: od wersji Visual Studio 2017 Update 3 (15.3) język obsługuje teraz async Main - tak długo, jak zwraca Task lub Task<T>. Więc teraz możesz to zrobić:

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

Semantyka wydaje się być taka sama jak GetAwaiter().GetResult() styl blokowania głównego wątku. Jednak nie ma jeszcze specyfikacji języka dla C # 7.1, więc jest to tylko założenie.

 416
Author: Stephen Cleary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Możesz to rozwiązać za pomocą prostej konstrukcji:

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

To spowoduje, że wszystko, co robisz, zostanie umieszczone w ThreadPool tam, gdzie chcesz (aby inne zadania, które rozpoczynasz/oczekujesz, nie próbowały ponownie dołączyć do wątku, którego nie powinni) i poczekaj, aż wszystko zostanie zrobione przed zamknięciem aplikacji konsoli. Nie ma potrzeby stosowania specjalnych pętli lub zewnętrznych bibliotek.

Edit: Włącz rozwiązanie Andrew dla nieobciążonych WYJĄTKÓW.

 371
Author: Chris Moschini,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-15 16:23:41

Możesz to zrobić bez konieczności korzystania z zewnętrznych bibliotek, wykonując następujące czynności:

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
Author: Steven Evers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-29 23:25:02

W C # 7.1 będziesz mógł wykonać poprawną asynchroniczną magistralę . Odpowiednie podpisy dla metody Main zostały rozszerzone do:

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

Dla np. możesz robić:

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

Podczas kompilacji, metoda punktu wejścia async zostanie przetłumaczona na call GetAwaitor().GetResult().

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

EDIT:

Aby włączyć funkcje języka C # 7.1, musisz kliknąć prawym przyciskiem myszy na projekcie i kliknij "Właściwości", a następnie przejdź do zakładki" Build". Tam kliknij przycisk Zaawansowane na dole:

Tutaj wpisz opis obrazka

Z menu rozwijanego wersja językowa wybierz "7.1" (lub dowolną wyższą wartość):

Tutaj wpisz opis obrazka

Domyślną wartością jest "latest major version", która oceniałaby (w momencie pisania tego tekstu) do C# 7.0, który nie obsługuje asynchronicznej wersji głównej w aplikacjach konsolowych.

 83
Author: nawfal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-21 17:08:11

Dodam ważną cechę, którą wszystkie inne odpowiedzi przeoczyły: anulowanie.

Jedną z ważniejszych rzeczy w TPL jest obsługa anulowania, a aplikacje konsolowe mają wbudowaną metodę anulowania (CTRL + C). Bardzo łatwo je połączyć. Tak układam wszystkie moje aplikacje konsoli asynchronicznej:

static void Main(string[] args)
{
    CancellationTokenSource cts = new CancellationTokenSource();
    
    System.Console.CancelKeyPress += (s, e) =>
    {
        e.Cancel = true;
        cts.Cancel();
    };

    MainAsync(args, cts.Token).GetAwaiter.GetResult();
}

static async Task MainAsync(string[] args, CancellationToken token)
{
    ...
}
 76
Author: Cory Nelson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-06 10:52:45

[[3]} C # 7.1 (using vs 2017 update 3) wprowadza async main

Możesz napisać:

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

Więcej szczegółów C# 7 Series, Part 2: Async Main

Update:

Może pojawić się błąd kompilacji:

Program nie zawiera statycznej "głównej" metody odpowiedniej dla punktu wejścia

Ten błąd jest spowodowany tym, że vs2017.3 jest domyślnie skonfigurowany jako c # 7.0, a nie c#7.1.

Należy jawnie zmodyfikować ustawienie swojego projekt do Ustawienia funkcji c # 7.1.

Możesz ustawić c # 7.1 za pomocą dwóch metod:

Metoda 1: Korzystanie z okna ustawień projektu:

  • Otwórz ustawienia swojego projektu
  • wybierz zakładkę budowania
  • kliknij przycisk Zaawansowane
  • wybierz żądaną wersję Jak pokazano na poniższym rysunku:

Tutaj wpisz opis obrazka

Method2: Modify PropertyGroup of .csprojekt

Dodaj to własność:

    <LangVersion>7.1</LangVersion>

Przykład:

    <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>    
 25
Author: M.Hassan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-25 17:22:09

Jeśli używasz C # 7.1 lub nowszego, wybierz odpowiedź nawfala i po prostu zmień typ zwracanej metody głównej na Task lub Task<int>. Jeśli nie:

Końcowy kod wygląda następująco:

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.
}
 20
Author: Şafak Gür,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-01 16:59:28

Nie potrzebowałem jeszcze tyle, ale kiedy używałem aplikacji konsolowej do szybkich testów i wymagałem asynchronizacji, rozwiązałem to tak:

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

    static async Task MainAsync(string[] args)
    {
        // Code here
    }
}
 19
Author: Johan Falk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-02 20:36:13

Do asynchronicznie wywołania zadania z Main, użyj

  1. Zadanie.Run() for. NET 4.5

  2. Zadanie.Fabryka.StartNew () dla. NET 4.0 (może wymagać Microsoft.Bcl.Biblioteka Async dla async i oczekujących słów kluczowych)

Szczegóły: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx

 7
Author: user3408030,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-11 21:25:49

W Main spróbuj zmienić wywołanie GetList na:

Task.Run(() => bs.GetList());
 4
Author: mysticdotnet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-07 14:19:01

Kiedy C# 5 CTP został wprowadzony, z pewnościąmożna oznaczyć Main przez async... chociaż generalnie nie było to dobrym pomysłem. Uważam, że zostało to zmienione przez wydanie VS 2013, aby stać się błędem.

Jeśli nie uruchomiłeś żadnego innego wątku foreground , twój program zakończy działanie po zakończeniu Main, nawet jeśli rozpoczął pracę w tle.

Co naprawdę próbujesz zrobić? Zauważ, że twoja metoda GetList() naprawdę nie musi być asynchroniczna w moment-to dodawanie dodatkowej warstwy bez prawdziwego powodu. Jest logicznie równoważne (ale bardziej skomplikowane niż):
public Task<List<TvChannel>> GetList()
{
    return new GetPrograms().DownloadTvChannels();
}
 4
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-21 08:50:35

Najnowsza wersja C # - C# 7.1 pozwala na tworzenie aplikacji konsoli asynchronicznej. Aby włączyć C# 7.1 w projekcie, musisz uaktualnić swój VS do co najmniej 15.3 I zmienić wersję C# na C# 7.1 lub C# latest minor version. Aby to zrobić, przejdź do Właściwości projektu - > Build - > Advanced - > Wersja Językowa.

Po tym, następujący kod będzie działać:

internal class Program
{
    public static async Task Main(string[] args)
    {
         (...)
    }
 4
Author: Kedrzu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-22 12:01:34

Na MSDN, dokumentacja dla Zadania.Run Method (Akcja) zawiera ten przykład, który pokazuje, jak uruchomić metodę asynchronicznie z 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

Zwróć uwagę na to stwierdzenie, które podąża za przykładem:

Przykłady pokazują, że zadanie asynchroniczne wykonuje na innym wątek niż główny wątek aplikacji.

Więc jeśli zamiast tego chcesz, aby zadanie zostało uruchomione w głównym wątku aplikacji, Zobacz odpowiedź by @ StephenCleary .

A co do wątku, na którym działa zadanie, zwróć również uwagę na komentarz Stephena na jego odpowiedź:

You can use a simple Wait or Result, and there ' s nothing wrong z tym. Należy jednak pamiętać, że istnieją dwie ważne różnice: 1) wszystkie async kontynuacje działają na puli wątków, a nie na głównym thread, oraz 2) wszelkie wyjątki są zawinięte w AggregateException.

(Zobacz Obsługa Wyjątków (Zadanie Parallel Library) dla tego, jak włączyć obsługę wyjątków do obsługi AggregateException.)


Wreszcie, na MSDN z dokumentacji dla zadania.Metoda Delay (TimeSpan) , ten przykład pokazuje jak uruchomić asynchroniczne zadanie, które zwraca wartość:

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

Zauważ, że zamiast przekazać delegate do Task.Run, możesz zamiast tego przekazać funkcję lambda w następujący sposób:

var t = Task.Run(async () =>
        {
            await Task.Delay(TimeSpan.FromSeconds(1.5));
            return 42;
        });
 3
Author: DavidRR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:53

W moim przypadku miałem listę zadań, które chciałem uruchomić w asynchronicznym trybie z mojej głównej metody, używałem tego w produkcji od dłuższego czasu i działa dobrze.

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 ...
}
 3
Author: user_v,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-26 05:34:32

Aby uniknąć zamrożenia podczas wywoływania funkcji gdzieś na stosie, która próbuje ponownie dołączyć bieżący wątek( który utknął w oczekiwaniu), musisz wykonać następujące czynności:

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

(obsada jest wymagana tylko do rozwiązania niejednoznaczności)

 1
Author: Nathan Phillips,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-07 14:25:28
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
     }
}
 0
Author: Kristaps Koks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-04 11:16:17