【问题标题】:Performance issuses with nested foreach loops嵌套 foreach 循环的性能问题
【发布时间】:2014-05-18 16:40:59
【问题描述】:

我不知道我面临的问题是由于嵌套循环还是由于其他原因(错误代码、大数据)。让我解释一下,希望有人能帮我弄清楚:-

我正在开发一个 Windows Phone 8 应用程序。在启动时,应用程序对 3 个不同的页面进行三个 httpclient 调用,然后将每个响应(即 html)转换为 xml,然后转换为 json 数据(OBS !!!这工作正常,没有问题)。在这个阶段,我想从这 3 个 json 中提取数据,并将提取的数据组合成一个新的 Json,以便稍后保存到 IsolatedStorage。为了进行这种提取,我使用了多个 foreach 循环,甚至有时使用 linq (我认为这是问题所在)。每个 foreach 循环可能会进行 50 到 >500 次迭代。提取过程大约需要 2 分钟,我认为这太多了。下面你可以从我的代码中看到一个代码 sn-p:-

public async Task LoadData()
    {

        //baseUri definition....

        await CookieHandler.GetCookies(baseUri);

        _reqPList=new RequestResponse();

        await _reqPList.GetResponse(baseUri, pList);  // First page request
        XmlConvertor.ConvertToXml(_reqPList.Response);  //First page convert to xml
        var phoneListResponse = XmlConvertor.XmlString;

        //Second and third page request and convert to xml

        var handler = new DataHandler();
        await handler.phoneListHandler(phoneListResponse);  //First page convert to Json
        //Second and third page convert to Json

        var jObject = JObject.Parse(handler.PhoneListJson);
        var jCollection = jObject["tbody"]["tr"].Children();

        //The start of extracting data

        using (var stream = new MemoryStream())
        using (var streamWriter=new StreamWriter(stream))
        using (_jsonW = new JsonTextWriter(streamWriter))
        {
                var watch = Stopwatch.StartNew();
            _jsonW.WriteStartArray();
            foreach (var item in jCollection)  //First foreach loop (100 iteration) using the first page
            {
                var arr = item.Value<JArray>("td");
                if (arr == null) continue;
                _jsonW.Formatting = Formatting.Indented;
                _jsonW.WriteStartObject();
                _jsonW.WritePropertyName("Name");
                _jsonW.WriteValue(arr[0].Value<string>());
                #region Info Array
                _jsonW.WritePropertyName("Info");
                _jsonW.WriteStartArray();

                // Do some extraction

                _jsonW.WriteEndArray();
                #endregion
                #region Schedule Array
                await ScheduleArrayAsync(item, handler.ScheduleJson, handler); // This method extract data from 2nd page
                #endregion
                #region Current activity
                CurrActivityAsync(item, handler.CurrActivitiesJson); // This method extract data from 3rd page
                #endregion
                _jsonW.WriteEndObject();
            }
            _jsonW.WriteEndArray();
                watch.Stop();
                var j = watch.ElapsedMilliseconds;

            await streamWriter.FlushAsync();
            stream.Position = 0;
            using (var streamReader=new StreamReader(stream))
            {
                var json = await streamReader.ReadToEndAsync();
            }
        }
        this.IsDataLoaded = true;
    }

    private void CurrActivityAsync(JToken token, string jString) This method is called 1 time only
    {
        var arr = token.Value<JArray>("td");
        var personNameArray = arr[0].Value<string>().Split(',');
        if (!_owner.Contains(personNameArray[0])) return;
        var jObject = JObject.Parse(jString);
        var jCollection = jObject["tbody"]["tr"].Children();

        _jsonW.WritePropertyName("Current activity");
        _jsonW.WriteStartArray();

        foreach (var array in from item in jCollection   // Second foreach loop (Almost 60 iteration)
            let array = item.Value<JObject>().Value<JArray>("td")
            where array != null && item.Children().Count() > 1
            select array)
        {
            _jsonW.WriteStartObject();

            // do some extraction 

            _jsonW.WriteEndObject();
        }
        _jsonW.WriteEndArray();
    }

    private async Task ScheduleArrayAsync(JToken token, string jString, DataHandler handler)  // This method is called 100 times.
    {
        var arr = token.Value<JArray>("td");
        var personNameArray = arr[0].Value<string>().Split(',');
        var jObject = JObject.Parse(jString);
        var trList = jObject["tbody"]["tr"];
        var selectedArray = default(JArray);
        var weekNumber = trList.SelectMany(tr => tr.SelectTokens("td[1].table.tbody.tr", false))
            .SelectMany(lk => lk.SelectTokens("td", false))
            .Where(j => (!j.HasValues) && (j.Value<string>().Contains("v")))
            .Select(lo => lo.Value<string>())
            .ToArray();

        var psTypeOne = trList.SelectMany(tr => tr.SelectTokens("td[0].a.#text", false))
            .Where(hashText => (hashText.Value<string>().Contains(personNameArray[0])) &&
                         (hashText.Value<string>().Contains(personNameArray[1])))
            .Select(td => td.Parent.Parent.Parent.Parent.Parent)
            .ToArray();


        if (psTypeOne != null)
        {
            _jsonW.WritePropertyName("Schedule");
            _jsonW.WriteStartArray();
            _jsonW.WriteStartObject();
            _jsonW.WritePropertyName("Week Number");
            _jsonW.WriteValue(weekNumber[0].Remove(0, 2));
            _jsonW.WritePropertyName("Week days");
            _jsonW.WriteStartArray();

            foreach (var day in selectedArray) // 7 itereation
            {
                if (day.SelectToken("table", false) == null) continue;
                _jsonW.WriteStartObject();
                _jsonW.WritePropertyName("Day name");
                var dayname = Convert.ToDateTime(day.Value<string>("@id").Remove(0, 8));
                _jsonW.WriteValue(dayname.DayOfWeek.ToString());
                _jsonW.WritePropertyName("Date");
                _jsonW.WriteValue(day.Value<string>("@id").Remove(0, 8));
                var assignments = day.SelectToken("table.tbody.tr", false);
                _jsonW.WritePropertyName("Assignments");
                _jsonW.WriteStartArray();

                //Foreach loop 2 start or Foreach
                foreach (var assignment in assignments) // between 1 and 5 iterations
                {
                    //Some extractions

                        if (info[0].Contains("slotInfo")) //Here we do some new webrequests and extractions
                        {
                            await _reqPList.GetSlotInfoPost(info[1]);
                            XmlConvertor.ConvertToXml(_reqPList.Response);
                            addedInfo = XmlConvertor.XmlString;
                            await handler.InfoHandler(addedInfo);
                            var jO = JObject.Parse(handler.InfoJson);
                            addedJToken = jO.SelectToken("td");

                            // Some extractions

                        }
                    }
                    else
                    {
                        // Some code
                    }
                }
                //Foreach loop 2 end
                //Some code to Close the Array
            }
            //Foreach loop 1 end
            //some code to Close the Array
        }
    }

现在考虑到所有这些迭代和方法调用,我可以优化代码以在 2 分钟内更快地完成处理吗?

编辑:-

让我给你一些关于我在代码开头解析的页面的更多信息。首先,它的页面属于我们在工作中使用的在线日程安排网站。他们没有任何 API 可以使用。这就是为什么我需要解析 Httpclient HtmlResponse 并将其转换为 xml 然后 json。

其次,这些页面中的每一个都具有非常复杂的结构和错误的命名。请看下面:-

<tr class="odd">
<td class="user" onmouseover="userInfo('469');" onmouseout="userInfo(0);" onmousemove="moveSlotInfo();"> User One</td> // User One is needed
<td id="day-469-2014-03-31" style="vertical-align: top;">
  <table class="ss" cellspacing="1">
    <tbody>
      <tr>
        <td class="as"> </td>
      </tr>
    </tbody>
  </table>
</td>
<td id="day-469-2014-04-01" style="vertical-align: top;">   // The date is needed
  <table class="ss" cellspacing="1">
    <tbody>
      <tr>
        <td class="as" style="color: #ffffff; background-color: #4040ff;" onmouseover="this.className=(document.week_vs_doctor.activityId.value==-1?'sd':'sp');slotInfo('177738',false);" onmouseout="this.className='as';slotInfo(0,false);" onmousemove="moveSlotInfo();">KAVAul</td>  // KAVAul is needed
      </tr>
    </tbody>
  </table>
</td>

OBS!!!这只是一个演示代码(我知道它不起作用)。

即使我将它转换为 Json,它仍然需要调整,这就是为什么我决定根据我的需要重写这个 json。看看这段代码 sn-p 因为它显示了我的需要。

[
{
"Name": "User Name",
"Info": [
  {
    "Signature": "un"
  },
  {
    "Group": "Group1"
  },
  {
    "E-mail": "user.name@group1.com"
  }
],
"Schedule": [
  {
    "Week Number": "14",
    "Week days": [
      {
        "Day name": "Monday",
        "Date": "2014-03-31",
        "Assignments": [
          {
            "Assignment name": "AAA",
            "Assignment time": "07:30 - 16:00",
            "Assignment provider": "BBB, 2014-03-07 16:42"
          }

OBS!!!我知道json数据需要关闭,这只是为了说明一点。

【问题讨论】:

  • handler.InfoHandler(addedInfo) 是做什么的?它是异步的有什么原因吗?
  • @siride handler.InfoHandler(addedInfo) 是对异步方法的调用,用于将数据从 xml 转换为 json。这段数据在我解析的原始3页中是没有的。
  • 这里为什么需要使用异步?以这种方式执行而不是同步执行,是否真的会占用大量性能?

标签: c# linq foreach json.net


【解决方案1】:

我认为使用htmlagility pack从httprequest响应中简单地遍历html可能更简单。

一个自定义类,它准确地代表你是具有可序列化属性的最终对象,当类被填充时调用 javascript 序列化器将所有序列化为一个 json 对象(你可以在线使用json converter 在你的自定义类中转换 json因此您可以简单地将 json 反序列化为自定义对象而不会出现问题,反之亦然)。

还有另一个重要的事情:数据维度。如果您处理少量数据,是否会更快,如果您处理大量数据,则需要更多时间来获得响应和更多时间来处理。

你能告诉我们哪些是涉及的页面,哪些是最终的 json 对象???

【讨论】:

  • 我之前尝试过 htmlagility Pack,但它不符合我的需要。无论如何,我不认为这是这里的问题。请查看已编辑的信息。你会看到将原始的 Json 转换为我的 json 并不是一件容易的事,需要手动提取数据。如果您认为我误解了您的答案,请提供一个示例,以便我更好地理解它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-09
  • 1970-01-01
相关资源
最近更新 更多