C# Language
非同期 - 待機中
サーチ…
前書き
C#では、 async
宣言されたメソッドは、I / Oベースの操作(Webアクセス、ファイルの操作など)を使用している場合、同期プロセス内でブロックされません。そのような非同期マークされたメソッドの結果は、 await
キーワードを使用してawait
ことができます。
備考
async
メソッドは、 void
、 Task
またはTask<T>
返すことができvoid
。
戻り値の型のTask
は、メソッドが終了するまで待機し、結果はvoid
になりvoid
。 Task<T>
は、メソッドの完了後にタイプT
から値を返します。
async
メソッドは、ほとんどすべての状況で、 void
ではなくTask
またはTask<T>
を返す必要がありvoid
。 async void
メソッドはedをawait
ことができません。これはさまざまな問題を引き起こします。 async
でvoid
を返す唯一のシナリオは、イベントハンドラの場合です。
async
/ await
、 async
メソッドを状態マシンに変換しawait
動作します。これは、現在の状態とコンテキスト(ローカル変数など)を格納するシーンの背後に構造を作成し、待っている待ち時間が完了するたびにMoveNext()
メソッドを公開して状態を進め(関連するコードを実行する)ようにします。
簡単な連続呼び出し
public async Task<JobResult> GetDataFromWebAsync()
{
var nextJob = await _database.GetNextJobAsync();
var response = await _httpClient.GetAsync(nextJob.Uri);
var pageContents = await response.Content.ReadAsStringAsync();
return await _database.SaveJobResultAsync(pageContents);
}
ここで注意すべき主なものは、すべてがいる間ということですawait
て、その時のために制御が戻ってシステムにもたらしている呼び出す- -メソッド内での流れが直線的であるとのために特別な処理を必要としない-edメソッドを非同期的に呼び出されました非同期。いずれかのメソッドが失敗した場合、例外は "期待どおり"に処理されます。この場合、メソッドの実行が中止され、例外がスタックに上がります。
試してみる/キャッチする/最後に
C#6.0以降、 await
キーワードはcatch
およびfinally
ブロック内で使用できるようになりました。
try {
var client = new AsyncClient();
await client.DoSomething();
} catch (MyException ex) {
await client.LogExceptionAsync();
throw;
} finally {
await client.CloseAsync();
}
C#6.0以前は、以下の行に沿って何かをする必要がありました。 6.0では、 Null Propagating演算子でヌルチェックもクリーンアップされています。
AsynClient client;
MyException caughtException;
try {
client = new AsyncClient();
await client.DoSomething();
} catch (MyException ex) {
caughtException = ex;
}
if (client != null) {
if (caughtException != null) {
await client.LogExceptionAsync();
}
await client.CloseAsync();
if (caughtException != null) throw caughtException;
}
async
作成されていないタスク( Task.Run
作成されたタスクTask.Run
)を待っている場合、周囲のtry / catchによって一見扱われても、タスクによってスローされた例外がデバッガによって破損することがあります。これは、デバッガがユーザコードに関して未処理であるとみなすために発生します。 Visual Studioには「Just My Code」というオプションがあり、このような状況でデバッガが壊れるのを防ぐために無効にすることができます。
正しい非同期動作のために4.5をターゲットにするWeb.configセットアップ。
web.config system.web.httpRuntimeは、非同期メソッドを再開する前にスレッドが要求コンテキストを確実にリネームするように4.5をターゲットにする必要があります。
<httpRuntime targetFramework="4.5" />
AsyncとAwaitは、4.5より前のASP.NETで未定義の動作をしています。 Async / awaitは、要求コンテキストを持たない任意のスレッドで再開します。ロード中のアプリケーションは、待ってからHttpContextにアクセスするnull参照例外が発生すると、ランダムに失敗します。 WebApiでHttpContext.Currentを使用すると、非同期のため危険です
同時通話
最初のawaitableタスクを起動し、それらを待っていることにより、同時に複数の呼び出しを待つことが可能です。
public async Task RunConcurrentTasks()
{
var firstTask = DoSomethingAsync();
var secondTask = DoSomethingElseAsync();
await firstTask;
await secondTask;
}
また、 Task.WhenAll
を使用して、複数のタスクを単一のTask
にグループ化することができます。タスクは、渡されたすべてのタスクが完了した時点で完了します。
public async Task RunConcurrentTasks()
{
var firstTask = DoSomethingAsync();
var secondTask = DoSomethingElseAsync();
await Task.WhenAll(firstTask, secondTask);
}
また、ループ内でこれを行うこともできます。たとえば、次のようにします。
List<Task> tasks = new List<Task>();
while (something) {
// do stuff
Task someAsyncTask = someAsyncMethod();
tasks.Add(someAsyncTask);
}
await Task.WhenAll(tasks);
Task.WhenAllで複数のタスクを待ってからタスクの結果を取得するには、単にタスクを再度待ちます。タスクはすでに完了しているので、結果は戻されます
var task1 = SomeOpAsync();
var task2 = SomeOtherOpAsync();
await Task.WhenAll(task1, task2);
var result = await task2;
また、 Task.WhenAny
同様に、並列に複数のタスクを実行するために使用することができるTask.WhenAll
供給されるタスクのいずれかが完了するときに、この方法が完了し差と、上記。
public async Task RunConcurrentTasksWhenAny()
{
var firstTask = TaskOperation("#firstTask executed");
var secondTask = TaskOperation("#secondTask executed");
var thirdTask = TaskOperation("#thirdTask executed");
await Task.WhenAny(firstTask, secondTask, thirdTask);
}
firstTask
、 secondTask
、またはthirdTask
いずれかが完了すると、 RunConcurrentTasksWhenAny
によって返されたTask
が完了します。
演算子と非同期キーワードを待つ
await
オペレータとasync
キーワードが一緒に来る:
awaitを使用する非同期メソッドは、 asyncキーワードで変更する必要があります。
反対のことは必ずしも真実ではありません。メソッドをawait
を本体に使用せずにasync
としてマークすることができます。
何await
実際に行うことは待望のタスクが完了するまで、コードの実行を一時停止することです。どんなタスクも待つことができます。
注意:何も返さない非同期メソッド(void)を待つことはできません。
実際には、実行が停止するだけでなく、スレッドが他の操作を実行するために自由になる可能性があるため、「サスペンド」という言葉は誤解を招くことがあります。ボンネットの下に、 await
コンパイラの魔法のビットによって実装されます。それは二つの部分に分割する方法を-前と後await
。後者の部分は、待っているタスクが完了したときに実行されます。
重要な細部を無視すると、コンパイラはおおまかにあなたのためにこれを行います:
public async Task<TResult> DoIt()
{
// do something and acquire someTask of type Task<TSomeResult>
var awaitedResult = await someTask;
// ... do something more and produce result of type TResult
return result;
}
次のようになる:
public Task<TResult> DoIt()
{
// ...
return someTask.ContinueWith(task => {
var result = ((Task<TSomeResult>)task).Result;
return DoIt_Continuation(result);
});
}
private TResult DoIt_Continuation(TSomeResult awaitedResult)
{
// ...
}
通常の方法は、次のように非同期に変換できます。
await Task.Run(() => YourSyncMethod());
これは、UIをフリーズせずにUIスレッドで長時間実行するメソッドを実行する必要がある場合に便利です。
しかし、ここでは非常に重要な発言があります。 非同期は常に並行(並列またはマルチスレッド)を意味するとは限りません。単一のスレッドであっawait
も、 async
- await
は非同期コードを許可します。たとえば、このカスタムタスクスケジューラを参照してください。このような「クレイジー」タスクスケジューラは、タスクを単にメッセージループ処理内で呼び出される関数に変えることができます。
我々は自分自身に尋ねる必要があります:どんなスレッドが私たちのメソッドDoIt_Continuation
継続を実行するのでしょうか?
デフォルトでは、 await
オペレータは現在のSynchronizationコンテキストで継続の実行をスケジュールします 。これは、UIスレッドでWinFormsとWPF継続がデフォルトで実行されることを意味します。なんらかの理由でこの動作を変更する必要がある場合は、 Task.ConfigureAwait()
メソッドを使用します。
await Task.Run(() => YourSyncMethod()).ConfigureAwait(continueOnCapturedContext: false);
待たずにタスクを返す
非同期操作を実行するメソッドは、次の場合にawait
を使用する必要はありません。
- メソッド内には非同期呼び出しが1つしかありません
- 非同期呼び出しはメソッドの最後にある
- タスク内で発生するかもしれないキャッチ/ハンドリング例外は必要ありません
Task
を返すこのメソッドについて考えてみましょう。
public async Task<User> GetUserAsync(int id)
{
var lookupKey = "Users" + id;
return await dataStore.GetByKeyAsync(lookupKey);
}
場合GetByKeyAsync
同じ署名有するGetUserAsync
(復帰Task<User>
)、方法を簡素化することができます。
public Task<User> GetUserAsync(int id)
{
var lookupKey = "Users" + id;
return dataStore.GetByKeyAsync(lookupKey);
}
この場合、メソッドは非同期操作を実行していてもasync
とマークする必要はありません。タスクによって返さGetByKeyAsync
それがされ、呼び出し元のメソッドに直接渡されawait
編。
重要 : Task
を待機する代わりにTask
を戻すと、 Task
を開始するメソッド内で例外をスローしないが、待っているメソッドで例外がスローされないため、メソッドの例外動作が変更されます。
public Task SaveAsync()
{
try {
return dataStore.SaveChangesAsync();
}
catch(Exception ex)
{
// this will never be called
logger.LogException(ex);
}
}
// Some other code calling SaveAsync()
// If exception happens, it will be thrown here, not inside SaveAsync()
await SaveAsync();
コンパイラが余分な非同期ステートマシンを生成するのを防ぐので、パフォーマンスが向上します。
非同期コードをブロックするとデッドロックが発生することがある
同期コンテキストを持つ環境でデッドロックが発生する可能性があるため、非同期呼び出しをブロックするのは悪い習慣です。ベストプラクティスはasyncを使用することです。たとえば、次のWindowsフォームコードはデッドロックを引き起こします。
private async Task<bool> TryThis()
{
Trace.TraceInformation("Starting TryThis");
await Task.Run(() =>
{
Trace.TraceInformation("In TryThis task");
for (int i = 0; i < 100; i++)
{
// This runs successfully - the loop runs to completion
Trace.TraceInformation("For loop " + i);
System.Threading.Thread.Sleep(10);
}
});
// This never happens due to the deadlock
Trace.TraceInformation("About to return");
return true;
}
// Button click event handler
private void button1_Click(object sender, EventArgs e)
{
// .Result causes this to block on the asynchronous call
bool result = TryThis().Result;
// Never actually gets here
Trace.TraceInformation("Done with result");
}
基本的に、非同期コールが完了すると、同期コンテキストが使用可能になるのを待ちます。しかし、イベントハンドラは、 TryThis()
メソッドが完了するのを待っている間に同期コンテキストに「ホールド」し、循環待ちを引き起こします。
これを修正するには、コードを次のように変更する必要があります。
private async void button1_Click(object sender, EventArgs e)
{
bool result = await TryThis();
Trace.TraceInformation("Done with result");
}
注意:イベントハンドラは、唯一の場所ですasync void
(あなたが待つことができないため、使用すべきであるasync void
法)。
Async / awaitは、マシンが追加の作業を行うことができる場合にのみパフォーマンスを向上させます
次のコードを考えてみましょう:
public async Task MethodA()
{
await MethodB();
// Do other work
}
public async Task MethodB()
{
await MethodC();
// Do other work
}
public async Task MethodC()
{
// Or await some other async work
await Task.Delay(100);
}
これは、
public void MethodA()
{
MethodB();
// Do other work
}
public void MethodB()
{
MethodC();
// Do other work
}
public void MethodC()
{
Thread.Sleep(100);
}
非同期/待機の主な目的は、マシンが追加の作業を実行できるようにすることです。たとえば、あるI / O操作の結果を待っている間に呼び出し側のスレッドが他の作業を実行できるようにすることです。この場合、呼び出しスレッドは決して実行できなかったより多くの作業を行うことはできません。したがって、単にMethodA()
、 MethodB()
、およびMethodC()
同期的に呼び出すだけではパフォーマンスは向上しません。