C#Parallel ForEach保持值

Zbo*_*one 1 c# parallel-processing foreach plinq

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

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);
            });

 }
Run Code Online (Sandbox Code Playgroud)

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

谢谢

Ric*_*ard 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
              };
Run Code Online (Sandbox Code Playgroud)

下面的AsParallel是TPL的执行手段LINQ运营商(Select,Where,...)被使用.


更新:现在有更多信息

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

  1. 该变量htmlconstatic由多个线程直接使用.这可能是您潜在的问题.只考虑两个输入值.第一个Get完成设置htmlcon,在该线程的调用copyMails开始之前,第二个线程Get完成其HTML GET并写入htmlcon.随着'电子邮件

  2. 该列表emailList也可以在没有多个线程锁定的情况下访问..NET(以及任何其他编程平台)中的大多数集合类型都不是线程安全的,您需要一次限制对单个线程的访问.

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

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

三点建议:

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

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

  3. 考虑一下电子邮件的正则表达式:有许多有效的电子邮件不匹配(例如,使用+提供多个逻辑地址:exmaple+one@gamil.com将被传递到example@gmail.com,然后可以用于本地规则).另外一个撇号(" '")是一个有效的角色(而且已知的人因为网站错误而拒绝了他们的地址而感到沮丧).

第二:相对直接的清理:

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);
        }
}
Run Code Online (Sandbox Code Playgroud)

我在这里:

  • 利用using语句自动清理资源.
  • 消除了所有可变的共享状态.
  • Regex明确记录为具有线程安全的实例方法.所以我只需要一个实例.
  • 删除了噪音:无需传递URL,ExtractEmails因为提取不使用URL.
  • Get现在只执行HTML get,ExtreactEMail只执行提取

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

真正的并发好处是HttpWebRequest.GetResponse用它们的异步等价物替换和读取响应流.

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

  • 无所事事ExtractEMails.
  • Get 现在是异步的,既不阻止HTTP GET也不读取结果.
  • SEbgWorker_DoWork直接使用Tasks来避免混合太多不同的方式来使用TPL.因为Get返回a Task<string>可以简单地继续(当它没有失败时 - 除非你指定,否则ContinueWith只有在前一个任务成功完成时才会继续):

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

public static async Task<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 = await req.GetResponseAsync())
    using (StreamReader SR = new StreamReader(resp.GetResponseStream())) {
        return await SR.ReadToEndAsync();
    }

}

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) {

    tasks = allSElist.OfType<string>()
                     .Select(url => {
        return Get(url, Settings1.Default.Proxyset)
                .ContinueWith(htmlContentTask => {
                    // No TaskContinuationOptions, so know always OK here
                    var htmlContent = htmlContentTask.Result;
                    var emails = ExtractEmails(htmlContent);
                    foreach (var email in emails) {
                        // No InvokeAsync on WinForms, so do this the old way.
                        Action dgeins = () => mailDataGrid.Rows.Insert(0, email, url);
                        mailDataGrid.BeginInvoke(dgeins);
                    }
                });
    });

    tasks.WaitAll();
}
Run Code Online (Sandbox Code Playgroud)