私は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
メソッドを許可しません。これは、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の言語仕様はまだないので、これは仮定にすぎません。
この単純な構成でこれを解決することができます。
class Program
{
static void Main(string[] args)
{
Task.Run(async () =>
{
// Do any async anything you need here without worry
}).GetAwaiter().GetResult();
}
}
それはあなたがそれを望むことになるThreadPoolのあなたがそれを望むことを望んでいる場所に(そして他のThreadが再び参加しようと試みないように)置き、そして全てが完了するまで待ってからConsoleアプリを閉じます。特別なループや外部のライブラリは必要ありません。
編集:キャッチされていない例外のためのアンドリューのソリューションを取り入れます。
次のようにしても、外部ライブラリを必要とせずにこれを実行できます。
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の大きな特徴の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)
{
...
}
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の言語機能を有効にするには、プロジェクトを右クリックして[プロパティ]をクリックし、[ビルド]タブに移動します。そこに、下部にある詳細ボタンをクリックしてください:
言語バージョンのドロップダウンメニューから、「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(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:プロジェクト設定ウィンドウを使用する:
方法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's answer を使用し、Mainメソッドの戻り型をTask
またはTask<int>
に変更するだけです。そうでない場合:
async Task MainAsync
Johanが言ったように を持ってください。.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から非同期的にタスクを呼び出すには、
.NET 4.5用のTask.Run()
.NET 4.0用のTask.Factory.StartNew()(非同期キーワードおよび待機キーワードにはMicrosoft.Bcl.Asyncライブラリーが必要な場合があります)
詳細: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
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)
{
(...)
}
C#5 CTPが導入されたとき、あなたは確かにMainをasync
...でマークすることができましたが、そうするのは一般に良い考えではありませんでした。これはVS 2013のリリースによってエラーになったために変更されたと思います。
他のフォアグラウンドスレッドを開始していない限り、バックグラウンド作業を開始していても、Main
が完了するとプログラムは終了します。
あなたは本当に何をしようとしていますか?あなたのGetList()
メソッドは現時点では非同期である必要はないことに注意してください - それは本当の理由で追加のレイヤを追加することです。これは論理的には等価です(ただしより複雑です)。
public Task<List<TvChannel>> GetList()
{
return new GetPrograms().DownloadTvChannels();
}
Mainで、GetListの呼び出しを次のように変更してみてください。
Task.Run(() => bs.GetList());
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の コメント にも注意してください。
あなたは単純な
Wait
やResult
を使うことができます。それで何も悪いことはありません。ただし、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
delegate
をTask.Run
に渡す代わりに、次のようにラムダ関数を渡すことができます。
var t = Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(1.5));
return 42;
});
現在のスレッドに再参加しようとするコールスタックのどこかで関数を呼び出すときにフリーズしないようにするには(Waitで止まっている)、次のことを行う必要があります。
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 ...
}