Programing

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

lottogame 2020. 2. 21. 22:00
반응형

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


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

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

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에서는 적절한 비동기 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으로 평가됩니다.


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

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

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

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

당신은 쓸 수 있습니다:

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

자세한 내용은 C # 7 시리즈, 파트 2 : 비동기 메인

최신 정보:

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

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

이 오류는 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.
}

Main에서 비동기 적으로 작업을 호출하려면 다음을 사용하십시오.

  1. .NET 4.5 용 Task.Run ()

  2. .NET 4.0 용 Task.Factory.StartNew () (비동기 및 대기 키워드에 Microsoft.Bcl.Async 라이브러리가 필요할 수 있음)

세부 사항 : http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx


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

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

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

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

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

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

최신 버전의 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)
    {
         (...)
    }

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

현재 스레드를 재결합하려고 시도하는 대기 스택에있는 함수를 호출 할 때 정지되는 것을 방지하려면 다음을 수행해야합니다.

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

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


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

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

참고 URL : https://stackoverflow.com/questions/9208921/cant-specify-the-async-modifier-on-the-main-method-of-a-console-app



반응형