콘솔 앱의 '메인'메소드에서 '비동기'수정자를 지정할 수 없습니다.


445

async수정자를 사용한 비동기 프로그래밍이 처음 입니다. Main콘솔 응용 프로그램의 메서드가 실제로 비동기 적으로 실행 되도록하는 방법을 알아 내려고합니다 .

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

나는 이것이 "위에서"비동기 적으로 실행되지 않는다는 것을 알고있다. 메소드에 async수정자를 지정할 수 없으므로 비동기 적으로 Main코드를 어떻게 실행할 수 main있습니까?


23
더 이상 C # 7.1에서는 그렇지 않습니다. 주요 방법은 비동기
적일

2
다음은 C # 7.1 블로그 게시물 공지 입니다. 비동기 메인 섹션을 참조하십시오 .
styfle

답변:


382

알다시피 VS11에서 컴파일러는 async Main메소드 를 허용하지 않습니다 . 이것은 비동기 CTP와 함께 VS2010에서 허용되었지만 권장되지는 않았습니다.

async / await비동기 콘솔 프로그램 에 대한 최근 블로그 게시물이 있습니다 . 소개 게시물의 배경 정보는 다음과 같습니다.

"await"는 awaitable이 완료되지 않은 것으로 보이면 비동기 적으로 작동합니다. 완료 될 때 메소드의 나머지를 실행하도록 awaitable에 지시 한 다음 async 메소드에서 리턴합니다 . Await는 메소드의 나머지를 awaitable에 전달할 때 현재 컨텍스트를 캡처합니다 .

나중에 Awaitable이 완료되면 나머지 캡처 방법 (캡처 된 컨텍스트 내)을 실행합니다.

이것이 콘솔 프로그램에서 문제가되는 이유입니다 async Main.

소개 게시물에서 비동기 메소드 는 완료되기 전에 호출자로 돌아갑니다 . 이것은 UI 응용 프로그램 (메소드 이벤트가 UI 이벤트 루프로 돌아옴) 및 ASP.NET 응용 프로그램 (메서드가 스레드를 반환하지만 요청은 계속 유지)에서 완벽하게 작동합니다. 콘솔 프로그램의 경우 제대로 작동하지 않습니다. Main이 OS로 돌아가므로 프로그램이 종료됩니다.

하나의 솔루션은 비동기 호환 콘솔 프로그램을위한 "메인 루프"라는 고유 한 컨텍스트를 제공하는 것입니다.

당신은 비동기 CTP와 기계가있는 경우 사용할 수 있습니다 GeneralThreadAffineContext에서 내 문서 \ 마이크로 소프트 비주얼 스튜디오 비동기 CTP \ 샘플 (C # 테스트) 단위 테스트 \ AsyncTestUtilities . 또는 내 Nito.AsyncEx NuGet 패키지AsyncContext 에서 사용할 수 있습니다 .

다음은 AsyncContext;을 사용하는 예입니다 . GeneralThreadAffineContext거의 동일한 사용법이 있습니다.

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

또는 비동기 작업이 완료 될 때까지 기본 콘솔 스레드를 차단할 수 있습니다.

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

GetAwaiter().GetResult(); 의 사용에 유의하십시오 . 이것은 피할 AggregateException사용하면 어떻게 포장 Wait()또는 Result.

업데이트, 2017년 11월 30일 : 비주얼 스튜디오 2017 업데이트 3 (15.3) 현재로는, 언어는 지금 지원 async Main- 한이 반환로 Task또는 Task<T>. 이제이 작업을 수행 할 수 있습니다.

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

시맨틱 GetAwaiter().GetResult()은 메인 스레드를 차단하는 스타일과 동일한 것으로 보입니다 . 그러나 C # 7.1에 대한 언어 사양은 아직 없으므로 가정에 불과합니다.


30
간단한 또는을 사용할 수 있으며 아무런 문제가 없습니다. 그러나 두 가지 중요한 차이점이 있습니다. 1) 모든 연속이 기본 스레드가 아닌 스레드 풀에서 실행되고 2) 예외가에 래핑됩니다 . WaitResultasyncAggregateException
Stephen Cleary

2
이것 (그리고 귀하의 블로그 게시물)까지 이것을 알아내는 데 실제로 문제가있었습니다. 이것은이 문제를 해결하는 가장 쉬운 방법이며, "install-package Nito.Asyncex"만으로 nuget 콘솔에 패키지를 설치할 수 있습니다.
ConstantineK

1
@StephenCleary : 빠른 응답 Stephen에게 감사합니다. 예외가 발생했을 때 누군가 디버거가 중단되는 것을 원하지 않는 이유를 이해하지 못합니다 . 디버깅하고 null 참조 예외를 가로 지르는 경우 문제가되는 코드 줄로 직접 이동하는 것이 좋습니다. VS는 동기 코드의 경우 "즉시"처럼 작동하지만 비동기 / 대기에서는 작동하지 않습니다.
그렉

6
C # 7.1에는 비동기 메인이 있으므로 @StephenCleary github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/에
Mafii

3
VS 2017에서 C # 7.1 버전을 사용하는 경우 여기에 표시된 것처럼<LangVersion>latest</LangVersion> csproj 파일 에 추가하여 프로젝트가 최신 버전의 언어를 사용하도록 구성해야합니다 .
Liam

359

이 간단한 구성 으로이 문제를 해결할 수 있습니다.

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

그러면 ThreadPool에서 원하는 모든 작업을 원하는 위치에 배치 할 수 있습니다 (따라서 시작 / 대기하는 다른 작업은 스레드에 다시 참여하려고 시도하지 않음). 모든 것이 완료 될 때까지 기다렸다가 콘솔 앱을 닫습니다. 특별한 루프 나 외부 라이브러리가 필요 없습니다.

편집 : 잡히지 않은 예외에 대한 Andrew의 솔루션을 통합하십시오.


3
이 접근법은 매우 분명하지만 예외를 래핑하는 경향이 있으므로 더 나은 방법을 찾고 있습니다.
abatishchev

2
@abatishchev 적어도 Task 내부에서 코드에서 try / catch를 사용해야합니다. 세분화되지 않으면 Task가 예외를 발생시키지 않도록하십시오. 당신은 실패 할 수있는 것들을 시도 / 캐치하여 마무리 문제를 피할 수 있습니다.
Chris Moschini

54
당신이 교체하는 경우 Wait()GetAwaiter().GetResult()당신에게 피할 수 있습니다 AggregateException일을 던질 때 래퍼.
앤드류 아 노트

7
async main이것이이 글을 쓰는 시점에서 C # 7.1에 도입 된 방식 입니다.
user9993

@ user9993 이 제안 에 따르면 그것은 사실이 아닙니다.
Sinjai

90

다음을 수행하여 외부 라이브러리 없이도이를 수행 할 수 있습니다.

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

7
마음에 베어 getListTask.Result도 차단 호출입니다 그래서 위의 코드가없이 작성 될 수있다 Task.WaitAll(getListTask).
do0g

27
또한 던져지면 실제 예외를 결정하기 위해 예외를 GetList포착 AggregateException하고 예외를 조사해야합니다. 당신은, 그러나, 호출 할 수 있습니다 GetAwaiter()를 얻을 수 TaskAwaiter을 위해 Task및 통화 GetResult(), 즉 그에 var list = getListTask.GetAwaiter().GetResult();. TaskAwaiter(또한 차단 호출) 에서 결과를 가져올 때 발생하는 예외는에서 래핑되지 않습니다 AggregateException.
do0g

1
.GetAwaiter (). GetResult는 내가 필요한 대답이었습니다. 그것은 내가하려는 일에 완벽하게 작동합니다. 아마 다른 곳에서도 이것을 사용할 것입니다.
Deathstalker

78

C # 7.1에서는 적절한 비동기 Main 을 수행 할 수 있습니다 . Main메소드 의 적절한 서명 이 다음과 같이 확장되었습니다.

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

예를 들어 당신은 할 수 있습니다 :

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

컴파일 타임에 비동기 진입 점 메소드는 call로 변환됩니다 GetAwaitor().GetResult().

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

편집하다:

C # 7.1 언어 기능을 사용하려면 프로젝트를 마우스 오른쪽 단추로 클릭하고 "속성"을 클릭 한 다음 "빌드"탭으로 이동해야합니다. 하단의 고급 버튼을 클릭하십시오.

여기에 이미지 설명을 입력하십시오

언어 버전 드롭 다운 메뉴에서 "7.1"(또는 더 높은 값)을 선택하십시오.

여기에 이미지 설명을 입력하십시오

기본값은 "최신 메이저 버전"이며이 글을 쓰는 시점에 콘솔 앱에서 비동기 메인을 지원하지 않는 C # 7.0으로 평가됩니다.


2
FWIW 이것은 Visual Studio 15.3 이상에서 사용할 수 있으며 현재 베타 / 미리보기로 제공됩니다. visualstudio.com/vs/preview
Mahmoud Al-Qudsi

잠깐만 ... 완전히 업데이트 된 설치를 실행 중이고 최신 옵션은 7.1입니다. 5 월에 7.2를 어떻게 얻었습니까?

대답은 내 것이었다. 7.2 (미리보기?)가 출시 될 것으로 생각 될 때까지 10 월 편집은 다른 누군가가 편집했습니다.
nawfal

1
헤딩-이 작업을 수행 할 때 디버그뿐만 아니라 모든 구성에 있는지 확인하십시오!
user230910

1
@ user230910 감사합니다. C # 팀에서 가장 이상한 선택 중 하나입니다.
nawfal

74

다른 모든 답변에서 간과 한 중요한 기능인 취소를 추가하겠습니다.

TPL의 가장 큰 장점 중 하나는 취소 지원이며 콘솔 응용 프로그램에는 취소 방법이 내장되어 있습니다 (CTRL + C). 그것들을 묶는 것은 매우 간단합니다. 이것이 내가 모든 비동기 콘솔 앱을 구성하는 방법입니다.

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

취소 토큰도 함께 전달해야합니까 Wait()?
Siewers

5
아니요, 비동기 코드에서 취소를 정상적으로 처리 할 수 ​​있기를 원하기 때문입니다. 에 전달 Wait()하면 비동기 코드가 완료 될 때까지 기다리지 않습니다. 대기를 중단하고 프로세스를 즉시 종료합니다.
코리 넬슨

확실합니까? 방금 시도해 보았으며 Wait()메서드가 동일한 토큰을 전달 하더라도 취소 요청이 가장 깊은 수준에서 처리되는 것처럼 보입니다 . 내가 말하려는 것은 아무런 차이가없는 것입니다.
Siewers

4
확실해. op가 끝나기를 기다리지 않고 op 자체를 취소하려고합니다. 정리 코드 마무리 또는 결과에 신경 쓰지 않는 한.
코리 넬슨

1
그래, 나는 그것을 얻는다 생각한다, 그것은 내 코드에서 아무런 차이를 만들지 않는 것처럼 보였다. 저를 던져 또 다른 것은 물론 대기 방법 지원 취소에 대한 예의의 ReSharper에서 힌트이었다) 그것은 내가 처음에 알아낼 수있는 OperationCancelledException, 던질 것 같이, 예에서 시도 캐치를 포함 할 수 있습니다
Siewers을

22

C # 7.1 (2017 업데이트 3 사용)에 비동기 메인이 도입되었습니다.

당신은 쓸 수 있습니다:

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

자세한 내용은 C # 7 Series, Part 2 : Async Main

최신 정보:

컴파일 오류가 발생할 수 있습니다.

프로그램에 진입 점에 적합한 정적 '메인'방법이 없습니다

이 오류는 vs2017.3이 기본적으로 c # 7.1이 아닌 c # 7.0으로 구성되어 있기 때문입니다.

c # 7.1 기능을 설정하려면 프로젝트 설정을 명시 적으로 수정해야합니다.

두 가지 방법으로 c # 7.1을 설정할 수 있습니다.

방법 1 : 프로젝트 설정 창 사용 :

  • 프로젝트의 설정을 엽니 다
  • 빌드 탭을 선택하십시오
  • 고급 버튼을 클릭하십시오
  • 다음 그림과 같이 원하는 버전을 선택하십시오.

여기에 이미지 설명을 입력하십시오

방법 2 : .csproj의 PropertyGroup을 수동으로 수정

이 속성을 추가하십시오 :

    <LangVersion>7.1</LangVersion>

예:

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

20

C # 7.1 이상을 사용하는 경우 nawfal의 답변으로 이동 하여 Main 메서드의 반환 유형을 Task또는로 변경하십시오 Task<int>. 그렇지 않은 경우 :

  • async Task MainAsync 요한처럼 말했다 .
  • do0g와 같이.GetAwaiter().GetResult() 기본 예외를 잡으려면 호출하십시오 .
  • 코리 같은 지원 취소 했다 .
  • 두 번째 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.
}

1
많은 훌륭한 프로그램이 CancelKeyPress를 처음으로 취소하므로 일단 ^ C를 누르면 정상적으로 종료되지만 초조 한 경우 ^ C는 비정상적으로 종료됩니다. 이 솔루션을 사용하면 e.Cancel = true무조건 조건으로 CancellationToken을 준수하지 않으면 프로그램을 수동으로 종료해야합니다 .
binki

19

아직 많이 필요하지는 않았지만 빠른 테스트를 위해 콘솔 응용 프로그램을 사용하고 비동기가 필요한 경우 다음과 같이 해결했습니다.

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

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

이 예제는 작업을 현재 컨텍스트에 예약 한 다음 기다려야 할 경우에 잘못 작동합니다 (예 : ConfigureAwait (false)를 추가하는 것을 잊어 버릴 수 있으므로 리턴 메소드가 기본 스레드에 예약 됨) ). 현재 스레드가 대기 상태이므로 교착 상태가 발생합니다.
Manushin Igor

6
사실이 아닙니다, @ManushinIgor. 최소한이 간단한 예에서는 SynchronizationContext메인 스레드와 관련 이 없습니다 . 없이도 ConfigureAwait(false)모든 연속이 스레드 풀에서 실행 되므로 교착 상태가 발생하지 않습니다 .
앤드류 아 노트


4

Main에서 GetList 호출을 다음과 같이 변경하십시오.

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

4

C # 5 CTP가 소개되었을 때 Main으로 표시 할 수 있습니다.async 는 있지만 일반적으로 그렇게하는 것은 좋지 않습니다. VS 2013의 출시로 인해 이것이 변경되었다고 생각합니다.

다른 포 그라운드 스레드를 시작하지 않은 Main경우 백그라운드 작업이 시작된 경우에도 프로그램이 완료되면 종료됩니다 .

당신은 정말로 무엇을 하려고합니까? 당신의 것을 참고 GetList()는 진짜 이유 별도의 레이어를 추가 있어요 - 방법은 정말 순간에 비동기 할 필요는 없습니다. 논리적으로 동일하지만 (보다 복잡합니다)

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

2
Jon, 목록의 항목을 비동기 적으로 가져 오려고하는데 왜 GetList 메소드에서 비동기가 적합하지 않습니까? 목록 자체가 아닌 비동기 목록의 항목을 수집해야하기 때문입니까? Main 메소드를 비동기로 표시하려고하면 "정적 Main 메소드를 포함하지 않습니다 ..."
danielovich

@danielovich : 무엇을 DownloadTvChannels()반환합니까? 아마 그것은 반환 Task<List<TvChannel>>하지 않습니까? 그렇지 않으면 기다릴 가능성이 없습니다. (Awaiter 패턴을 감안할 수 있지만 가능하지는 않습니다.)이 Main방법은 여전히 ​​정적이어야 합니다. 수정자를 수정 자로 대체 했 습니까? staticasync
Jon Skeet

예, 당신이 말한 것처럼 Task <..>를 반환합니다. 어떻게 Main 메서드 서명에 비동기를 넣으려고해도 오류가 발생합니다. VS11 미리보기 비트에 앉아 있습니다!
danielovich

@danielovich : void 반환 유형이 있습니까? 그냥 public static async void Main() {}? 그러나 이미을 DownloadTvChannels()반환하는 Task<List<TvChannel>>경우 아마도 이미 비동기 적이므로 다른 레이어를 추가 할 필요가 없습니다. 이것을 신중하게 이해하는 것이 좋습니다.
Jon Skeet

1
@nawfal : 되돌아 보면 VS2013이 출시되기 전에 변경되었다고 생각합니다. C # 7이 변경 될지 확실하지 않습니다 ...
Jon Skeet

4

최신 버전의 C #-C # 7.1에서는 비동기 콘솔 앱을 만들 수 있습니다. 프로젝트에서 C # 7.1을 사용하려면 VS를 15.3 이상으로 업그레이드하고 C # 버전을 C# 7.1또는로 변경해야 C# latest minor version합니다. 이를 수행하려면 프로젝트 특성-> 빌드-> 고급-> 언어 버전으로 이동하십시오.

이 후 다음 코드가 작동합니다.

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

3

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의 답변 에 대한 Stephen의 의견 도 주목하십시오 .

간단한 또는을 사용할 수 있으며 아무런 문제가 없습니다. 그러나 두 가지 중요한 차이점이 있습니다. 1) 모든 연속이 기본 스레드가 아닌 스레드 풀에서 실행되고 2) 예외가에 래핑됩니다 .WaitResultasyncAggregateException

(을 처리하기 위해 예외 처리를 통합하는 방법 은 예외 처리 (작업 병렬 라이브러리) 를 참조하십시오 AggregateException.)


마지막으로 MSDN의 Task.Delay Method (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

a delegate를 전달하는 Task.Run대신 다음과 같이 람다 함수를 전달할 수 있습니다.

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

1

현재 스레드를 대기중인 (대기 상태에있는) 호출 스택 아래로 함수를 호출 할 때 정지되는 것을 방지하려면 다음을 수행해야합니다.

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

(캐스트는 모호성을 해결하기 위해서만 필요합니다)


감사; Task.Run은 GetList ()의 교착 상태를 유발하지 않습니다. 잠깐,이 답변 shold에는 더 많은 찬성이 있습니다 ...
Stefano d' Antonio

1

내 경우에는 기본 방법에서 비동기로 실행하고 싶었던 작업 목록이 있었으며 프로덕션 에서이 작업을 꽤 오랫동안 사용해 왔으며 정상적으로 작동합니다.

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 ...
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.