コンソールアプリの 'Main'メソッドで 'async'修飾子を指定できません


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修飾子を指定することはできないためMainmain非同期でコードを実行するにはどうすればよいですか?


23
これは、C#7.1では当てはまりません。主なメソッドは非同期にすることができます
Vasily Sliounaiev

2
これがC#7.1ブログ投稿のお知らせです。Async Mainというタイトルのセクションを参照してください。
スタイル

回答:


382

あなたが発見したように、VS11ではコンパイラはasync Mainメソッドを許可しません。これは、VS2010で非同期CTPを使用して許可されました(ただし、推奨されませんでした)。

特にasync / await非同期コンソールプログラムに関する最近のブログ投稿があります。これが紹介記事の背景情報です。

「await」は、awaitableが完了していないことを確認すると、非同期で動作します。完了すると、残りのメソッドを実行するようにawaitableに指示し、asyncメソッドから戻ります。Awaitは、メソッドの残りの部分をawaitableに渡すときに現在のコンテキストもキャプチャします

その後、awaitableが完了すると、(キャプチャされたコンテキスト内で)asyncメソッドの残りの部分が実行されます。

これが、コンソールプログラムで次の問題がある理由ですasync Main

紹介記事から、非同期メソッドは完了する前に呼び出し元に戻ることを覚えておいてください。これは、UIアプリケーション(メソッドは単にUIイベントループに戻る)とASP.NETアプリケーション(メソッドはスレッドから戻るが、要求を存続させる)で完全に機能します。コンソールプログラムではうまく機能しません。メインがOSに戻るため、プログラムは終了します。

1つの解決策は、独自のコンテキストを提供することです。非同期互換性のあるコンソールプログラムの「メインループ」です。

非同期CTPを備えたマシンがある場合は、My Documents \ Microsoft Visual Studio Async CTP \ Samples(C#Testing)Unit Testing \ AsyncTestUtilitiesGeneralThreadAffineContextから使用できます。または、私の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日:のVisual Studio 2017のアップデート3(15.3)のように、言語がサポートされるようになりましたasync Main-限り、それは返すようTaskTask<T>。したがって、これを行うことができます:

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

セマンティクスGetAwaiter().GetResult()は、メインスレッドをブロックするスタイルと同じように見えます。ただし、C#7.1の言語仕様はまだないため、これは前提にすぎません。


30
あなたはできるシンプルを使用しWaitたりResult、と何も間違ったことはしてあります。ただし、2つの重要な違いがあることに注意してください。1)すべてのasync継続はメインスレッドではなくスレッドプールで実行されます。2)例外はでラップされますAggregateException
スティーブンクリアリー2014年

2
これ(とあなたのブログ投稿)までこれを理解するのに本当に問題がありました。これは、この問題を解決する最も簡単な方法であり、「インストールパッケージ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ファイルに追加することにより、プロジェクトが最新バージョンの言語を使用するように構成されていることを確認する必要がありました
リアム

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

2
@abatishchevコードでは、少なくともTask.Runの内部でtry / catchを使用する必要があります。細かく設定しない場合は、例外がタスクにフロートされないようにする必要があります。失敗する可能性のあるものにtry / catchを配置することで、ラップアップの問題を回避できます。
Chris Moschini、2015年

54
あなたが交換した場合Wait()GetAwaiter().GetResult()、あなたは避けるだろうAggregateException物事が投げる時にラッパーを。
Andrew Arnott、2015年

7
これはasync main、この記事の執筆時点でC#7.1に導入されている方法です。
user9993 2017年

@ user9993 この提案によると、それは正確には当てはまりません。
シンジャイ

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 2014

27
また、GetListスローする場合は、をキャッチしAggregateExceptionてその例外を問い合わせ、スローされた実際の例外を判別する必要があります。ただし、呼び出すことができますGetAwaiter()取得するTaskAwaiterためにTask、通話GetResult()、すなわちその上var list = getListTask.GetAwaiter().GetResult();TaskAwaiter(ブロッキング呼び出しでもある)から結果を取得する場合、スローされた例外はにラップされませんAggregateException
do0g 14

1
.GetAwaiter()。GetResultが必要な答えでした。それは私がやろうとしていたことに対して完全に機能します。私はおそらく他の場所でもこれを使用します。
デスストーカー2017年

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
マフムードアル・Qudsi

ちょっと待って...完全に更新されたインストールを実行していて、最新のオプションは7.1です... 7.2は5月にどうやって入手できますか?

5月の答えは私のものでした。10月の編集は、7.2(プレビュー?)がリリースされたと私が思うまでに、他の誰かが編集したものです。
nawfal 2017年

1
確認してください-これを行うときにデバッグするだけでなく、すべての構成にあることを確認してください!
user230910 2018

1
@ user230910ありがとう。C#チームによる最も奇妙な選択肢の1つ。
nawfal 2018

74

他のすべての回答が見落としている重要な機能、つまりキャンセルを追加します。

TPLの大きな点の1つはキャンセルのサポートであり、コンソールアプリにはキャンセルの方法が組み込まれています(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、2014年

5
いいえ。非同期コードでキャンセルを適切に処理できるようにする必要があるためです。に渡した場合Wait()、非同期コードが終了するのを待たず、待機を停止してすぐにプロセスを終了します。
コリーネルソン

よろしいですか?試してみたところWait()、同じトークンがメソッドに渡された場合でも、キャンセル要求が最も深いレベルで処理されているようです。私が言おうとしていることは、それは何の違いももたらさないようです。
Siewers、2014年

4
私は確信しています。オペレーションが完了するのを待つのではなく、オペレーション自体をキャンセルしたい。クリーンアップコードの仕上げやその結果を気にしない限り。
コリーネルソン

1
ええ、私はそれを理解していると思います、それは私のコードに何の違いももたらさなかったようです。私をコースから外したもう1つのことは、キャンセルをサポートするwaitメソッドについてのReSharperの丁寧なヒントでした;)最初に理解できなかったOperationCancelledExceptionをスローするため、サンプルにtry catchを含めることができます
Siewers

22

C#7.1(2017 vs Update 3を使用)は非同期メインを導入します

あなたは書ける:

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

詳細については、C#7シリーズ、パート2:非同期メイン

更新:

コンパイルエラーが発生する場合があります。

プログラムには、エントリポイントに適した静的な「Main」メソッドが含まれていません

このエラーは、vs2017.3がデフォルトでc#7.1ではなくc#7.0として構成されていることが原因です。

プロジェクトの設定を明示的に変更して、c#7.1機能を設定する必要があります。

次の2つの方法で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>。そうでない場合:

最終的なコードは次のようになります。

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を押すと正常にシャットダウンしますが、焦っている場合、2回目の^ Cは異常終了します。このソリューションでe.Cancel = trueは、無条件であるためCancellationTokenを順守できない場合、プログラムを手動で強制終了する必要があります。
binki 2018年

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)、すべての継続はスレッドプールで実行されるため、デッドロックは発生しません。
Andrew Arnott



4

C#5 CTPが導入されたとき、Mainを...でマークすること確かに可能asyncですが、一般的にそうすることはお勧めできませんでした。これはVS 2013のリリースによってエラーになるように変更されたと思います。

他のフォアグラウンドスレッドを開始していない限り、プログラムはMain、バックグラウンド作業を開始していても、完了時に終了します。

あなたは本当に何をしようとしていますか?あなたのGetList()メソッドは現時点では本当に非同期である必要はないことに注意してください-それは本当の理由ではなく追加のレイヤーを追加しています。これは論理的には同等です(しかしより複雑です)。

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

2
ジョン、リストの項目を非同期で取得したいのに、なぜGetListメソッドで非同期が適切でないのですか?リスト自体ではなく、リストasync 'のアイテムを収集する必要があるためですか?Mainメソッドを非同期でマークしようとすると、「静的Mainメソッドが含まれていません...」と表示されます
danielovich

@danielovich:何がDownloadTvChannels()戻りますか?おそらくそれはそれを返しTask<List<TvChannel>>ませんか?そうでなければ、あなたがそれを待つことができるとは考えられません。(可能性はありますが、awaiterパターンが与えられれば、可能性は低いです。)Mainメソッドに関しては-それはまだ静的である必要があります... おそらく修飾子を修飾子に置き換えましたか?staticasync
Jon Skeet、2012

はい、それはあなたが言ったようにタスク<..>を返します。Mainメソッドのシグネチャにどのように非同期を配置しようとしても、エラーがスローされます。VS11プレビュービットに座っています。
danielovich

@danielovich:戻り値の型が無効でも?ただpublic static async void Main() {}?ただし、DownloadTvChannels()すでにを返している場合Task<List<TvChannel>>、おそらくそれはすでに非同期であるため、別のレイヤーを追加する必要はありません。これを注意深く理解する価値があります。
Jon Skeet、2012

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メソッド(アクション)のドキュメントがこの例を提供しており、メソッドをから非同期で実行する方法を示しています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

そして、タスクが実行されるスレッドに関して、彼の答えに関するスティーブンのコメントにも注意してください:

あなたはできるシンプルを使用しWaitたりResult、と何も間違ったことはしてあります。ただし、2つの重要な違いがあることに注意してください。1)すべてのasync継続はメインスレッドではなくスレッドプールで実行されます。2)例外はすべてにラップされますAggregateException

例外処理を組み込んでを処理する方法については、例外処理(タスク並列ライブラリ)を参照してください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

にを渡す代わりdelegateTask.Run、次のようなラムダ関数を渡すことができます:

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

1

現在のスレッド(Waitでスタックしている)に再度参加しようとするコールスタックのどこかで関数を呼び出すときにフリーズを回避するには、次の操作を行う必要があります。

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()のデッドロックが発生することはありません。この回答には、さらに多くの賛成投票があります...
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.