ファイルから読み取った複数の入力 URL を使用してGoogle PageSpeed Insights APIを呼び出し.txt
、結果を.csv
.
私はコンソール アプリを作成して、これらのリクエストを実行し、それらが戻ってきてリストに追加し、それらがすべて完了したらlist
、.csv
ファイルに書き込みます ( .csv
) に即座に応答します。
私のコードは下にあり、最適化にはほど遠いです。私は JavaScript のバックグラウンドを持っており、通常は Web ワーカーや他のマネージドの新しいスレッドを使用しないため、C# でも同じことをしようとしていました。
WebRequest
のを実行して、複数のスレッドを使用せずにそれらをコレクション (または出力ファイル) に書き込み、それらをすべて並行して実行し、次の要求を処理する前に各要求が返されるのを待つ必要はありませんか?BackgroundWorker
が必要な場合、これを行うクリーン コードの方法は何ですか?static void Main(string[] args)
{
Console.WriteLine("Begin Google PageSpeed Insights!");
appMode = ConfigurationManager.AppSettings["ApplicationMode"];
var inputFilePath = READ_WRITE_PATH + ConfigurationManager.AppSettings["InputFile"];
var outputFilePath = READ_WRITE_PATH + ConfigurationManager.AppSettings["OutputFile"];
var inputLines = File.ReadAllLines(inputFilePath).ToList();
if (File.Exists(outputFilePath))
{
File.Delete(outputFilePath);
}
List<string> outputCache = new List<string>();
foreach (var line in inputLines)
{
var requestDataFromPsi = CallPsiForPrimaryStats(line);
Console.WriteLine($"Got response of {requestDataFromPsi.Result}");
outputCache.Add(requestDataFromPsi.Result);
}
var writeTask = WriteCharacters(outputCache, outputFilePath);
writeTask.Wait();
Console.WriteLine("End Google PageSpeed Insights");
}
private static async Task<string> CallPsiForPrimaryStats(string url)
{
HttpWebRequest myReq = (HttpWebRequest)WebRequest.Create($"https://www.googleapis.com/pagespeedonline/v2/runPagespeed?url={url}&strategy=mobile&key={API_KEY}");
myReq.Method = WebRequestMethods.Http.Get;
myReq.Timeout = 60000;
myReq.Proxy = null;
myReq.ContentType = "application/json";
Task<WebResponse> task = Task.Factory.FromAsync(
myReq.BeginGetResponse,
asyncResult => myReq.EndGetResponse(asyncResult),
(object)null);
return await task.ContinueWith(t => ReadStreamFromResponse(t.Result));
}
private static string ReadStreamFromResponse(WebResponse response)
{
using (Stream responseStream = response.GetResponseStream())
using (StreamReader sr = new StreamReader(responseStream))
{
string jsonResponse = sr.ReadToEnd();
dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonResponse);
var psiResp = new PsiResponse()
{
Url = jsonObj.id,
SpeedScore = jsonObj.ruleGroups.SPEED.score,
UsabilityScore = jsonObj.ruleGroups.USABILITY.score,
NumberResources = jsonObj.pageStats.numberResources,
NumberHosts = jsonObj.pageStats.numberHosts,
TotalRequestBytes = jsonObj.pageStats.totalRequestBytes,
NumberStaticResources = jsonObj.pageStats.numberStaticResources,
HtmlResponseBytes = jsonObj.pageStats.htmlResponseBytes,
CssResponseBytes = jsonObj.pageStats.cssResponseBytes,
ImageResponseBytes = jsonObj.pageStats.imageResponseBytes,
JavascriptResponseBytes = jsonObj.pageStats.javascriptResponseBytes,
OtherResponseBytes = jsonObj.pageStats.otherResponseBytes,
NumberJsResources = jsonObj.pageStats.numberJsResources,
NumberCssResources = jsonObj.pageStats.numberCssResources,
};
return CreateOutputString(psiResp);
}
}
static async Task WriteCharacters(List<string> inputs, string outputFilePath)
{
using (StreamWriter fileWriter = new StreamWriter(outputFilePath))
{
await fileWriter.WriteLineAsync(TABLE_HEADER);
foreach (var input in inputs)
{
await fileWriter.WriteLineAsync(input);
}
}
}
private static string CreateOutputString(PsiResponse psiResponse)
{
var stringToWrite = "";
foreach (var prop in psiResponse.GetType().GetProperties())
{
stringToWrite += $"{prop.GetValue(psiResponse, null)},";
}
Console.WriteLine(stringToWrite);
return stringToWrite;
}
問題は、これがまだ動作が遅いことです。オリジナルは 20 分かかりましたが、リファクタリング後も 20 分かかりました。おそらくPageSpeed APIのGoogleによって、どこかで抑制されているようです。https://www.google.com/、https://www.yahoo.com/、https://www.bing.com/およびその他の 18に電話してテストしました。一度に約 5 件のリクエストしか処理しないというボトルネック。5 つのテスト URL を実行してからファイルに書き込んで繰り返すようにリファクタリングを試みましたが、プロセスはわずかに高速化されました。
static void Main(string[] args) { MainAsync(args).Wait(); }
static async Task MainAsync(string[] args)
{
Console.WriteLine("Begin Google PageSpeed Insights!");
appMode = ConfigurationManager.AppSettings["ApplicationMode"];
var inputFilePath = READ_WRITE_PATH + ConfigurationManager.AppSettings["InputFile"];
var outputFilePath = READ_WRITE_PATH + ConfigurationManager.AppSettings["OutputFile"];
var inputLines = File.ReadAllLines(inputFilePath).ToList();
if (File.Exists(outputFilePath))
{
File.Delete(outputFilePath);
}
var tasks = inputLines.Select(line => CallPsiForPrimaryStats(line));
var outputCache = await Task.WhenAll(tasks);
await WriteCharacters(outputCache, outputFilePath);
Console.WriteLine("End Google PageSpeed Insights");
}
private static async Task<string> CallPsiForPrimaryStats(string url)
{
HttpWebRequest myReq = (HttpWebRequest)WebRequest.Create($"https://www.googleapis.com/pagespeedonline/v2/runPagespeed?url={url}&strategy=mobile&key={API_KEY}");
myReq.Method = WebRequestMethods.Http.Get;
myReq.Timeout = 60000;
myReq.Proxy = null;
myReq.ContentType = "application/json";
Console.WriteLine($"Start call: {url}");
// Try using `HttpClient()` later
//var myReq2 = new HttpClient();
//await myReq2.GetAsync($"https://www.googleapis.com/pagespeedonline/v2/runPagespeed?url={url}&strategy=mobile&key={API_KEY}");
Task<WebResponse> task = Task.Factory.FromAsync(
myReq.BeginGetResponse,
myReq.EndGetResponse,
(object)null);
var result = await task;
return ReadStreamFromResponse(result);
}
private static string ReadStreamFromResponse(WebResponse response)
{
using (Stream responseStream = response.GetResponseStream())
using (StreamReader sr = new StreamReader(responseStream))
{
string jsonResponse = sr.ReadToEnd();
dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonResponse);
var psiResp = new PsiResponse()
{
Url = jsonObj.id,
SpeedScore = jsonObj.ruleGroups.SPEED.score,
UsabilityScore = jsonObj.ruleGroups.USABILITY.score,
NumberResources = jsonObj.pageStats.numberResources,
NumberHosts = jsonObj.pageStats.numberHosts,
TotalRequestBytes = jsonObj.pageStats.totalRequestBytes,
NumberStaticResources = jsonObj.pageStats.numberStaticResources,
HtmlResponseBytes = jsonObj.pageStats.htmlResponseBytes,
CssResponseBytes = jsonObj.pageStats.cssResponseBytes,
ImageResponseBytes = jsonObj.pageStats.imageResponseBytes,
JavascriptResponseBytes = jsonObj.pageStats.javascriptResponseBytes,
OtherResponseBytes = jsonObj.pageStats.otherResponseBytes,
NumberJsResources = jsonObj.pageStats.numberJsResources,
NumberCssResources = jsonObj.pageStats.numberCssResources,
};
return CreateOutputString(psiResp);
}
}
static async Task WriteCharacters(IEnumerable<string> inputs, string outputFilePath)
{
using (StreamWriter fileWriter = new StreamWriter(outputFilePath))
{
await fileWriter.WriteLineAsync(TABLE_HEADER);
foreach (var input in inputs)
{
await fileWriter.WriteLineAsync(input);
}
}
}
private static string CreateOutputString(PsiResponse psiResponse)
{
var stringToWrite = "";
foreach (var prop in psiResponse.GetType().GetProperties())
{
stringToWrite += $"{prop.GetValue(psiResponse, null)},";
}
Console.WriteLine(stringToWrite);
return stringToWrite;
}
これらの複数の WebRequest を実行して、複数のスレッドを使用せずにそれらをコレクション (または出力ファイル) に書き込み、それらをすべて並行して実行し、次の要求を処理する前に各要求が返されるのを待つ必要はありませんか?
はい; あなたが探しているのは、Task.WhenAll
.
コールバックでこれを行うよりクリーンな方法はありますか?
async
/await
はコールバックよりもクリーンです。JavaScript は、コールバックから promise ( Task<T>
C#に類似)、async
/ await
( C# のasync
/await
に非常に類似) に移行しました。両方の言語で最もクリーンなソリューションは、現在async
/await
です。
ただし、主に下位互換性が原因で、C# にはいくつかの落とし穴があります。
1) 非同期コンソール アプリでは、Main
メソッドをブロックする必要があります。一般的に、これは非同期コードでブロックする必要がある唯一の時間です。
static void Main(string[] args) { MainAsync(args).Wait(); }
static async Task MainAsync(string[] args)
{
あなたが持ってたらasync MainAsync
方法を、あなたは使用することができTask.WhenAll
、非同期並行処理のために:
...
var tasks = inputLines.Select(line => CallPsiForPrimaryStats(line));
var outputCache = await Task.WhenAll(tasks);
await WriteCharacters(outputCache, outputFilePath);
...
2) 使用しないでくださいContinueWith
。これは低レベルの危険な API です。await
代わりに使用:
private static async Task<string> CallPsiForPrimaryStats(string url)
{
...
Task<WebResponse> task = Task.Factory.FromAsync(
myReq.BeginGetResponse,
myReq.EndGetResponse,
(object)null);
var result = await task;
return ReadStreamFromResponse(result);
}
3) 多くの場合、より「非同期に適した」タイプが利用可能です。この場合、HttpClient
代わりに使用することを検討してくださいHttpWebRequest
。コードがかなりすっきりすることがわかります。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加