我正在开发一款可在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。如何在数据网格视图中保持正确匹配?
由于
答案 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 operators(Select
,Where
,...)的实现。
更新:现在提供更多信息
首先您的代码中存在许多问题:
变量htmlcon
为static
,但由多个线程直接使用。这可能是您潜在的问题。只考虑两个输入值。第一个Get
完成设置htmlcon
,在该线程调用copyMails
之前,第二个线程的Get
完成其HTML GET并写入htmlcon
。使用`email
也可以访问列表emailList
,而不会被多个线程锁定。 .NET(以及任何其他编程平台)中的大多数集合类型不线程安全,您需要一次限制对单个线程的访问。
您正在混合各种方法中的各种活动。考虑应用单一责任原则。
Thread.Sleep
来处理异常?!如果你不能处理异常(即解决条件),那么什么都不做。在这种情况下,如果操作抛出,则Parallel.Foreach
将抛出:直到您定义如何处理HTML GET失败为止。
三点建议:
根据我的经验,干净的代码(强迫程度)使事情变得更容易:格式的细节 无所谓(一个真正的支撑风格更好,但一致性是关键)。刚刚经历 并清理格式显示问题#1和#2。
良好的命名。除非是a,否则不要缩写超过几行代码的任何东西
域名的重要术语。例如。并行循环中的action参数的s
实际上是一个url
所以称之为。这种事情立即使代码更容易理解。
考虑电子邮件的正则表达式:有许多有效的电子邮件不匹配(例如,使用+
提供多个逻辑地址:exmaple+one@gamil.com
将被传递到{{1}然后可以用于本地规则)。另外一个撇号(“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);
}
}
语句自动清理资源。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任务链,当你进行此更改时,一个并行操作的结果将是其他的输入数据,并且它作为许多如何修改代码的选项之一避免共享状态。
更好的方法是修改代码以避免共享状态,如何根据您的实现做很多选项,而不仅仅是任务链。