web-dev-qa-db-ja.com

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

私は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内のコードを非同期に実行するにはどうすればよいですか?

387
danielovich

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

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

"await"がawaitableが完了していないことを認識した場合、それは非同期的に機能します。メソッドが完了したら残りのメソッドを実行し、その後非同期メソッドからreturnsを実行するようにawaitableに指示します。メソッドがメソッドの残りの部分をawaitableに渡すと、Awaitは現在のコンテキストもキャプチャします。

後で、待機が完了すると、(キャプチャされたコンテキスト内で)残りの非同期メソッドが実行されます。

これがasync Mainを持つコンソールプログラムの問題である理由はここにあります:

非同期メソッドは、完了する前に呼び出し側に対してreturnされることを私たちの紹介記事から思い出してください。これは、UIアプリケーション(メソッドはUIイベントループに戻るだけ)およびASP.NETアプリケーション(メソッドはスレッドから返されますが、要求は維持されます)で完全に機能します。コンソールプログラムではそれほどうまくいきません。メインはOSに戻ります - あなたのプログラムは終了します。

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

Async CTPを搭載したマシンをお持ちの場合は、マイドキュメント\ Microsoft Visual Studio Async CTP\Samples(C#テスト)Unit Testing\AsyncTestUtilities _のGeneralThreadAffineContextを使用できます。あるいは、 my 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()の使用に注意してください。これにより、Wait()またはAggregateExceptionを使用した場合に発生するResultの折り返しが回避されます。

更新、2017-11-30:Visual Studio 2017 Update 3(15.3)以降、この言語はTaskまたはasync Mainを返す限り、Task<T>をサポートするようになりました。だから今、これを行うことができます:

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

その意味は、メインスレッドをブロックするGetAwaiter().GetResult()スタイルと同じように見えます。しかし、C#7.1の言語仕様はまだないので、これは仮定にすぎません。

325
Stephen Cleary

この単純な構成でこれを解決することができます。

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

それはあなたがそれを望むことになるThreadPoolのあなたがそれを望むことを望んでいる場所に(そして他のThreadが再び参加しようと試みないように)置き、そして全てが完了するまで待ってからConsoleアプリを閉じます。特別なループや外部のライブラリは必要ありません。

編集:キャッチされていない例外のためのアンドリューのソリューションを取り入れます。

343
Chris Moschini

次のようにしても、外部ライブラリを必要とせずにこれを実行できます。

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

私は他のすべての答えが見落としていた重要な機能を追加するつもりです:キャンセル。

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

C#7.1では、適切な非同期メインを実行できるようになります。 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();
}

コンパイル時に、非同期エントリポイントメソッドはGetAwaitor().GetResult()を呼び出すように変換されます。

詳細: https://blogs.msdn.Microsoft.com/mazhou/2017/05/30/c-7-series-part-2-async-main

編集:

C#7.1の言語機能を有効にするには、プロジェクトを右クリックして[プロパティ]をクリックし、[ビルド]タブに移動します。そこに、下部にある詳細ボタンをクリックしてください:

enter image description here

言語バージョンのドロップダウンメニューから、「7.1」(またはそれ以上の値)を選択します。

enter image description here

デフォルトは「最新メジャーバージョン」で、この記事の執筆時点ではC#7.0と評価されていますが、コンソールアプリケーションでは非同期メインをサポートしていません。

65
nawfal

まだあまり必要としていませんが、クイックテストにコンソールアプリケーションを使用して非同期を要求したときは、次のように解決しました。

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

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

C#7.1(vs 2017 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:プロジェクト設定ウィンドウを使用する:

  • プロジェクトの設定を開く
  • [ビルド]タブをクリックします
  • 詳細設定ボタンをクリック
  • 次の図に示すように、必要なバージョンを選択してください。

enter image description here

方法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>    
17
M.Hassan

C#7.1以降を使用している場合は、 nawfal's answer を使用し、Mainメソッドの戻り型をTaskまたはTask<int>に変更するだけです。そうでない場合:

  • async Task MainAsyncJohanが言ったように を持ってください。
  • 基礎となる例外 do0gが言ったように をキャッチするためにその.GetAwaiter().GetResult()を呼び出します。
  • サポートキャンセル Coryが言ったように
  • 2番目の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.
}
17
Şafak Gür

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

7
user3408030

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

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

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

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

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

Mainで、GetListの呼び出しを次のように変更してみてください。

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

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

例に続く次の文に注意してください。

例は、非同期タスクがメインアプリケーションスレッドとは異なるスレッドで実行されることを示しています。

そのため、代わりにタスクをメインアプリケーションスレッドで実行する場合は、 答え by @ StephenCleary .

そしてタスクが実行されるスレッドに関して、彼の答えについてStephenの コメント にも注意してください。

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

AggregateExceptionを処理するための例外処理の組み込み方法については、 例外処理(タスク並列ライブラリ) を参照してください。)


最後に、MSDNで Task.Delayメソッド(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;
        });
3
DavidRR

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

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

(キャストはあいまいさを解決するためにのみ必要です)

1
Nathan Phillips

私の場合は、私のメインメソッドとは非同期に実行したいジョブのリストを持っていましたが、これを本番環境でかなり以前から使用していて、問題なく動作しています。

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