C#のイベントベースの非同期。一般的なリファクタリングは可能ですか?
-
05-07-2019 - |
質問
WebClientなどの一部のAPIは、イベントベースの非同期パターンを使用します。これは単純に見えますが、おそらく疎結合アプリ(UIのBackgroundWorkerなど)ではうまく機能しますが、連鎖はあまりよくありません。
たとえば、非同期処理がブロックされないようにマルチスレッド化されたプログラムを次に示します。 (これがサーバーアプリで数百回呼び出されることを想像してください-ThreadPoolスレッドをブロックしたくないです。)3つのローカル変数(" state")を取得し、2つの非同期呼び出しを行い、最初のリクエストが2番目のリクエストに送られます(したがって、それらは並行して実行できません)。状態も変化する可能性があります(追加しやすい)。
WebClientを使用すると、次のようになります(または、クロージャーのように動作するオブジェクトの束を作成することになります):
using System;
using System.Net;
class Program
{
static void onEx(Exception ex) {
Console.WriteLine(ex.ToString());
}
static void Main() {
var url1 = new Uri(Console.ReadLine());
var url2 = new Uri(Console.ReadLine());
var someData = Console.ReadLine();
var webThingy = new WebClient();
DownloadDataCompletedEventHandler first = null;
webThingy.DownloadDataCompleted += first = (o, res1) => {
if (res1.Error != null) {
onEx(res1.Error);
return;
}
webThingy.DownloadDataCompleted -= first;
webThingy.DownloadDataCompleted += (o2, res2) => {
if (res2.Error != null) {
onEx(res2.Error);
return;
}
try {
Console.WriteLine(someData + res2.Result);
} catch (Exception ex) { onEx(ex); }
};
try {
webThingy.DownloadDataAsync(new Uri(url2.ToString() + "?data=" + res1.Result));
} catch (Exception ex) { onEx(ex); }
};
try {
webThingy.DownloadDataAsync(url1);
} catch (Exception ex) { onEx(ex); }
Console.WriteLine("Keeping process alive");
Console.ReadLine();
}
}
このイベントベースの非同期パターンをリファクタリングする一般的な方法はありますか? (つまり、このような各APIの詳細な拡張メソッドを記述する必要はありませんか?)BeginXXXとEndXXXは簡単にしますが、このイベントの方法は何の方法も提供していないようです。
解決
F#
を調べてください。 F#
は、«ワークフロー»でこのコーディングを自動化できます。特徴。 '08 PDCプレゼンテーションの F#
は、 async
と呼ばれる標準ライブラリワークフローを使用して非同期Web要求を処理しました。これは、 BeginXXX
/ EndXXX
パターンを使用しますが、イベントパターンのワークフローをそれほど簡単に記述したり、缶詰のパターンを見つけたりできます。また、F#はC#でうまく機能します。
他のヒント
過去には、イテレータメソッドを使用してこれを実装しました。別のURLを要求するたびに、「yield return」を使用します。制御をメインプログラムに戻す。リクエストが終了すると、メインプログラムはイテレータを呼び出して次の作業を実行します。
C#コンパイラを効果的に使用して、ステートマシンを作成しています。利点は、イテレータメソッドで通常のC#コードを記述して、全体を駆動できることです。
using System;
using System.Collections.Generic;
using System.Net;
class Program
{
static void onEx(Exception ex) {
Console.WriteLine(ex.ToString());
}
static IEnumerable<Uri> Downloader(Func<DownloadDataCompletedEventArgs> getLastResult) {
Uri url1 = new Uri(Console.ReadLine());
Uri url2 = new Uri(Console.ReadLine());
string someData = Console.ReadLine();
yield return url1;
DownloadDataCompletedEventArgs res1 = getLastResult();
yield return new Uri(url2.ToString() + "?data=" + res1.Result);
DownloadDataCompletedEventArgs res2 = getLastResult();
Console.WriteLine(someData + res2.Result);
}
static void StartNextRequest(WebClient webThingy, IEnumerator<Uri> enumerator) {
if (enumerator.MoveNext()) {
Uri uri = enumerator.Current;
try {
Console.WriteLine("Requesting {0}", uri);
webThingy.DownloadDataAsync(uri);
} catch (Exception ex) { onEx(ex); }
}
else
Console.WriteLine("Finished");
}
static void Main() {
DownloadDataCompletedEventArgs lastResult = null;
Func<DownloadDataCompletedEventArgs> getLastResult = delegate { return lastResult; };
IEnumerable<Uri> enumerable = Downloader(getLastResult);
using (IEnumerator<Uri> enumerator = enumerable.GetEnumerator())
{
WebClient webThingy = new WebClient();
webThingy.DownloadDataCompleted += delegate(object sender, DownloadDataCompletedEventArgs e) {
if (e.Error == null) {
lastResult = e;
StartNextRequest(webThingy, enumerator);
}
else
onEx(e.Error);
};
StartNextRequest(webThingy, enumerator);
}
Console.WriteLine("Keeping process alive");
Console.ReadLine();
}
}