[C#] 콘솔 앱의 ‘메인’메소드에서 ‘비동기’수정자를 지정할 수 없습니다.

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있습니까?



답변

알다시피 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에 대한 언어 사양은 아직 없으므로 가정에 불과합니다.


답변

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

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

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

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


답변

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

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


답변

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으로 평가됩니다.


답변

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

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


답변

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>    


답변

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