複数の Async HttpWebRequest を起動するには、バックグラウンド ワーカーまたは複数のスレッドが必要ですか?

エリック・D・ジョンソン

全体的な目標

ファイルから読み取った複数の入力 URL を使用Google PageSpeed Insights APIを呼び出し.txt、結果を.csv.

私が試したこと

私はコンソール アプリを作成して、これらのリクエストを実行し、それらが戻ってきてリストに追加し、それらがすべて完了したらlist.csvファイルに書き込みます ( .csv) に即座に応答します。

私のコードは下にあり、最適化にはほど遠いです。私は JavaScript のバックグラウンドを持っており、通常は Web ワーカーや他のマネージドの新しいスレッドを使用しないため、C# でも同じことをしようとしていました。

  1. これらの複数WebRequestを実行して、複数のスレッドを使用せずにそれらをコレクション (または出力ファイル) に書き込み、それらをすべて並行して実行し、次の要求を処理する前に各要求が返されるのを待つ必要はありませんか?
  2. コールバックでこれを行うよりクリーンな方法はありますか?
  3. スレッドまたは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;
}

更新: Stephen Cleary からのリファクタリング後ヒント

問題は、これがまだ動作が遅いことです。オリジナルは 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]

編集
0

コメントを追加

0

関連記事

Related 関連記事

ホットタグ

アーカイブ