私はc#5の非同期機能を初めて使用します。私はこれら2つの実装の違いを理解しようとしています:
実装1:
private void Start()
{
foreach(var url in urls)
{
ParseHtml(url);
}
}
private async void ParseHtml(string url)
{
var query = BuildQuery(url); //BuildQuery is some helper method
var html = await DownloadHtml(query);
//...
MyType parsedItem = ParseHtml(html);
SaveTypeToDB(parsedItem);
}
private async Task<string> DownloadHtml(string query)
{
using (var client = new HttpClient())
try
{
var response = await client.GetAsync(query);
return (await response.Content.ReadAsAsync<string>());
}
catch (Exception ex)
{
Logger.Error(msg, ex);
return null;
}
}
実装2:
private void DoLoop()
{
foreach(var url in urls)
{
Start(url);
}
}
private async void Start(url)
{
await Task.Run( () => ParseHtml(url)) ;
}
private void ParseHtml(string url)
{
var query = BuildQuery(url); //BuildQuery is some helper method
var html = DownloadHtml(query);
//...
MyType parsedItem = ParseHtml(html);
SaveTypeToDB(parsedItem);
}
private string DownloadHtml(string query)
{
using (var client = new WebClient())
{
try
{
return client.DownloadString(query);
}
catch (Exception ex)
{
Logger.Error(msg, ex);
return null;
}
}
}
コード内のメソッドに必要な「非同期」署名が少なくなるため、2番目の実装を使用したいと思います。 HttpClientクラスを使用することと、新しいタスクを使用して代わりにそれを待つことの利点を理解しようとしていますか?
2つの実装に違いはありますか?
コード内のメソッドに必要な「非同期」署名が少なくなるため、2番目の実装を使用したいと思います。
それは非常に奇妙な正当化のように聞こえます。あなたは基本的に「やや非同期的に」実行しようとしているので、それを明確にしてみませんか?
2つの実装に違いはありますか?
絶対に。 2番目の実装は、WebClient.DownloadString
がブロックしている間、スレッドを拘束し、要求が完了するのを待ちます。最初のバージョンにはブロックされたスレッドがありません-リクエストが終了したときに起動し続けることに依存しています。
さらに、Logger.Error
呼び出しを検討してください。非同期バージョンでは、元の呼び出しコードのコンテキストで実行されます。したがって、これがたとえばWindowsフォームUIにある場合は、引き続きUIスレッドを使用し、UI要素などにアクセスできます。2番目のバージョンでは、スレッドプールスレッドで実行するため、必要になります。 UIスレッドにマーシャリングしてUIを更新します。
あなたのasync void
メソッドはほぼ確実にすべきではないasync void
であることに注意してください。イベントハンドラのシグネチャに準拠するために、async
メソッドがvoid
を返すようにする必要があります。それ以外の場合はすべて、Task
を返します。これにより、呼び出し元はタスクが終了したことを確認したり、例外を処理したりできます。
また、非同期にHttpClient
を使用する必要はないことに注意してください。代わりにWebClient.DownloadStringTaskAsync
を使用できるため、最終的なメソッドは次のようになります。
private async Task<string> DownloadHtml(string query)
{
using (var client = new WebClient())
{
try
{
return await client.DownloadStringTaskAsync(query);
}
catch (Exception ex)
{
Logger.Error(msg, ex);
return null;
}
}
}
サーバーアプリケーションの場合、async
は、ブロックされたスレッドの数を最小限に抑えることです。つまり、スレッドプールの効率を高め、プログラムをより多くのユーザーに拡張できるようにします。
スレッド数を気にする必要がないクライアントアプリケーションの場合、async
は、I/Oを実行するときにUIをスムーズに実行し続けるための比較的簡単な方法を提供します。
ボンネットの下にあるTask.Run
とは大きく異なります。
UIの応答性を維持するなど、呼び出し元のスレッドに意味のあることがある場合にのみ、非同期処理のメリットが得られます。呼び出し元のスレッドがタスクを開始するだけで、タスクが終了するまで待つだけの場合、プロセスの実行速度は遅くなります。
2番目の実装はタスクを開始しますが、他に何もせずにタスクが完了するのを待ちます。この方法では、メリットはありません。
環境については説明しませんが、レスポンシブを維持する必要のあるUIがある場合は、Start()が非同期として宣言されておらず、待機しないことを除いて、メソッド1の実装は問題ありません。
private async Task StartAsync()
{
foreach (var url in urls)
{
await ParseHtml(url)
}
}
これは、次のようにイベントハンドラーから呼び出すことができます。
private async void OnButton1_clicked(object sender, ...)
{
await StartAsync();
}
注:ParseHtmlの前にはawaitがあります。次のhtmlは、前の解析が終了した後に解析されます。ただし、解析は非同期であるため、呼び出し元のスレッド(UIスレッド?)は、ユーザー入力への応答などの他のことを実行できます。
ただし、parseHTML関数を同時に実行できる場合は、次のコードが望ましいでしょう。おそらくより高速です。
private async Task StartAsync()
{
var tasks = new List<Task>()
foreach (var url in urls)
{
tasks.Add(ParseHtml(url));
}
// now you have a sequence of Tasks scheduled to be run, possibly simultaneously.
// you can do some other processing here
// once you need to be certain that all tasks are finished await Task.WhenAll(...)
await Task.WhenAll(tasks);
// Task.WhenAls(...) returns a Task, hence you can await for it
// the return of the await is a void
}
<TResult
>を返します