C#Parallel ForEach保持值

时间:2012-07-21 13:09:23

标签: c# foreach parallel-processing plinq

我正在开发一款可在Google搜索结果网址中搜索电子邮件地址的应用。问题是它需要将每个页面中找到的值+它找到电子邮件的URL返回到包含2列的数据网格视图:电子邮件和URL。 我正在使用Parallel.ForEach,但它当然会返回随机网址,而不是真正找到电子邮件的网址。

public static string htmlcon;  //htmlsource

public static List<string> emailList = new List<string>();

public static string Get(string url, bool proxy)
    {
        htmlcon = "";

        try
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            if (proxy)
                req.Proxy = new WebProxy(proxyIP + ":" + proxyPort);
            req.Method = "GET";
            req.UserAgent = Settings1.Default.UserAgent;
            if (Settings1.Default.EnableCookies == true)
            {
                CookieContainer cont = new CookieContainer();
                req.CookieContainer = cont;
            }
            WebResponse resp = req.GetResponse();
            StreamReader SR = new StreamReader(resp.GetResponseStream());
            htmlcon = SR.ReadToEnd();

            Thread.Sleep(400);
            resp.Close();
            SR.Close();
        }
        catch (Exception)
        {
            Thread.Sleep(500);
        }

        return htmlcon;

    }



  private void copyMails(string url)
    {    
        string emailPat = @"(\b[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b)";
        MatchCollection mailcol = Regex.Matches(htmlcon, emailPat, RegexOptions.Singleline);
        foreach (Match mailMatch in mailcol)
        {
            email = mailMatch.Groups[1].Value;
            if (!emailList.Contains(email))
            {
                emailList.Add(email);



                  Action dgeins = () => mailDataGrid.Rows.Insert(0, email, url);
                  mailDataGrid.BeginInvoke(dgeins);

             }
        }
     }

  private void SEbgWorker_DoWork(object sender, DoWorkEventArgs e)
 {
     //ALOT OF IRRELEVAMT STUFF BEING RUN

     Parallel.ForEach(allSElist.OfType<string>(), (s) =>
        {
            //Get URL
            Get(s, Settings1.Default.Proxyset);


            //match mails 1st page 
            copyMails(s);
            });

 }

所以这就是它:我执行一个Get请求(其中“s”是列表中的URL),然后从URL的html源执行copyMails。它使用正则表达式来复制电子邮件。 如果我没有并行执行,则会返回datagridview中每封电子邮件的正确URL。如何在数据网格视图中保持正确匹配?

由于

2 个答案:

答案 0 :(得分:7)

最好使用PLINQ的Where来过滤(伪代码):

var results = from i in input.AsParallel()
              let u = get the URL from i
              let d = get the data from u
              let v = try get the value from d
              where v is found
              select new {
                Url = u,
                Value = v
              };

AsParallel下方意味着使用了TPL的LINQ operatorsSelectWhere,...)的实现。


更新:现在提供更多信息

首先您的代码中存在许多问题:

  1. 变量htmlconstatic,但由多个线程直接使用。这可能是您潜在的问题。只考虑两个输入值。第一个Get完成设置htmlcon,在该线程调用copyMails之前,第二个线程的Get完成其HTML GET并写入htmlcon。使用`email

  2. 也可以访问列表emailList,而不会被多个线程锁定。 .NET(以及任何其他编程平台)中的大多数集合类型线程安全,您需要一次限制对单个线程的访问。

  3. 您正在混合各种方法中的各种活动。考虑应用单一责任原则。

  4. Thread.Sleep来处理异常?!如果你不能处理异常(即解决条件),那么什么都不做。在这种情况下,如果操作抛出,则Parallel.Foreach将抛出:直到您定义如何处理HTML GET失败为止。

  5. 三点建议:

    1. 根据我的经验,干净的代码(强迫程度)使事情变得更容易:格式的细节 无所谓(一个真正的支撑风格更好,但一致性是关键)。刚刚经历 并清理格式显示问题#1和#2。

    2. 良好的命名。除非是a,否则不要缩写超过几行代码的任何东西 域名的重要术语。例如。并行循环中的action参数的s实际上是一个url 所以称之为。这种事情立即使代码更容易理解。

    3. 考虑电子邮件的正则表达式:有许多有效的电子邮件不匹配(例如,使用+提供多个逻辑地址:exmaple+one@gamil.com将被传递到{{1}然后可以用于本地规则)。另外一个撇号(“example@gmail.com”)是一个有效的角色(被网站挫败的网站通过错误来拒绝他们的地址)。

    4. 第二次:相对直接的清理:

      '

      我在这里:

      • 利用public static string Get(string url, bool proxy) { HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url); if (proxy) { req.Proxy = new WebProxy(proxyIP + ":" + proxyPort); } req.Method = "GET"; req.UserAgent = Settings1.Default.UserAgent; if (Settings1.Default.EnableCookies == true) { CookieContainer cont = new CookieContainer(); req.CookieContainer = cont; } using (WebResponse resp = req.GetResponse()) using (StreamReader SR = new StreamReader(resp.GetResponseStream())) { return SR.ReadToEnd(); } } private static Regex emailMatcher = new Regex(@"(\b[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b)", RegexOptions.Singleline); private static string[] ExtractEmails(string htmlContent) { return emailMatcher.Matches(htmlContent).OfType<Match> .Select(m => m.Groups[1].Value) .Distinct() .ToArray(); } private void SEbgWorker_DoWork(object sender, DoWorkEventArgs e) { Parallel.ForEach(allSElist.OfType<string>(), url => { var htmlContent = Get(url, Settings1.Default.Proxyset); var emails = ExtractEmails(htmlContent); foreach (var email in emails) { Action dgeins = () => mailDataGrid.Rows.Insert(0, email, url); mailDataGrid.BeginInvoke(dgeins); } } 语句自动清理资源。
      • 消除所有可变共享状态。
      • using被明确记录为具有线程安全实例方法。所以我只需要一个实例。
      • 删除了噪音:无需将网址传递给Regex,因为提取不使用网址。
      • ExtractEmails现在只执行HTML get,Get仅提取

      第三:以上将阻止最慢操作的线程:HTML GET。

      真正的并发优势是替换ExtreactEMail并使用异步等价物读取响应流。

      使用HttpWebRequest.GetResponse将是.NET 4中的答案,但您需要直接使用Task并自行编码,因为Stream不提供任何StreamReader / BeginABC方法对。但.NET 4.5几乎就在这里,所以应用一些EndABC / async

      • await无所事事。
      • ExtractEMails现在是异步的,既不会阻止HTTP GET,也不会读取结果。
      • Get直接使用Tasks来避免混合使用太多不同的方法来使用TPL。由于SEbgWorker_DoWork返回Get可以简单地继续(当它没有失败时 - 除非您另行指定Task<string>只会在上一个任务成功完成后才会继续):

      这应该适用于.NET 4.5,但如果没有一组有效的URL,我将无法测试。

      ContinueWith

答案 1 :(得分:0)

  

public static string htmlcon; // htmlsource

     

public static List emailList = new List();

问题是因为这些成员htmlcon和emailList是线程之间和迭代之间的共享资源。 Parallel.ForEach中的每个迭代都是并行执行的。这就是为什么你有奇怪的行为。

如何解决问题:

  • 修改代码并尝试在没有静态变量或共享状态的情况下实现它。

作为一个选项是从Parallel.ForEach更改为TPL任务链,当你进行此更改时,一个并行操作的结果将是其他的输入数据,并且它作为许多如何修改代码的选项之一避免共享状态。

  • 使用锁定或并发集合。你的htmlcon变量可以变得易变,但是你可以使用列表锁定或并发集合。

更好的方法是修改代码以避免共享状态,如何根据您的实现做很多选项,而不仅仅是任务链。