【发布时间】:2016-01-05 00:54:31
【问题描述】:
我试图从网站上抓取一些数据。这是我的课:
class ClosureCraziness
{
public string SaveFolder { get; set; }
public void Save(Dictionary<string, string> idToWebLocation)
{
var tasks = new List<Task>();
foreach (var kvp in idToWebLocation)
{
var task = new Task(() => Download(kvp.Key, kvp.Value));
task.Start();
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
}
void Download(string id, string location)
{
var filename = $"{id}.html";
string source = string.Empty;
try
{
source = GetSource(location);
}
catch (Exception e)
{
// handle exception
}
var path = Path.Combine(SaveFolder, filename);
using (var sw = new StreamWriter(path))
sw.Write(source);
}
string GetSource(string location)
{
using (var client = new WebClient())
{
return client.DownloadString(location);
}
}
}
当我执行时,我会得到如下内容。您会注意到文件的内容(下载的源)与名称不匹配:
磁盘上的文件名 | File Contents
apple.html <html> apple </html>
orange.html <html> orange </html>
pear.html <html> peach </html>
peach.html <html> peach </html>
grape.html <html> apple </html>
plum.html <html> plum </html>
(我不知道如何很好地格式化)
起初我很困惑,因为磁盘上的文件名是正确的,我确定我的Dictionary<string, string> 格式正确(我检查了 6 次,所有不同的方式),这意味着 Id 与 web 位置的关联很好.
我想这可能是一个关闭问题,回忆了Eric Lippert schooling me on the implementation of foreach。所以我尝试了:
public void Save(Dictionary<string, string> idToWebLocation)
{
var tasks = new List<Task>();
foreach (var kvp in idToWebLocation)
{
var innerKvp = kvp;
var task = new Task(() => Download(innerKvp.Key, innerKvp.Value));
task.Start();
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
}
而且,为了安全起见:
public void Save(Dictionary<string, string> idToWebLocation)
{
var tasks = new List<Task>();
foreach (var kvp in idToWebLocation)
{
var innerKvp = kvp;
var id = innerKvp.Key;
var loc = innerKvp.Value;
var task = new Task(() => Download(id, loc));
task.Start();
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
}
还有,因为谁知道:
public void Save(Dictionary<string, string> idToWebLocation)
{
var tasks = new List<Task>();
foreach (var kvp in idToWebLocation)
{
var innerKvp = kvp;
var task = new Task(() =>
{
var id = innerKvp.Key;
var loc = innerKvp.Value;
Download(id, loc);
});
task.Start();
tasks.Add(task);
}
Task.WaitAll(tasks.ToArray());
}
但这些都不起作用。显然,我对如何编译这段代码缺乏了解,但我的意思是,到底发生了什么。
似乎var filename = $"{id}.html"; 和source = GetSource(location); 之间的某个地方location 正在发生变化。我很确定代码是线程安全的,没有共享状态,对吧?
但显然不是,因为当我同步遍历字典时,一切都按预期工作。
也许我在这里遗漏了一些基本点,关于外壳、线程或内存等。我不知道,但我的办公桌上满是头发,我快秃顶了。
【问题讨论】:
-
如果你怀疑字典是线程的问题(我认为也不是,它不参与并行执行),试试 ConcurrentDictionary。仔细看看源文件,它们真的有你期望的内容吗?
-
@MartinMaat 他们没有我期望的内容,这就是问题所在。除非您的意思是当我运行同步时,在这种情况下,是的,我非常仔细地检查了它们。另外 - 我无法想象字典是这里的问题。
-
它是针对 .NET 4.5.2 编译的 C# 6($"you can see the string {interpolation} at use here" - 这太棒了)。但我真的不认为这是一个关闭问题了。也许。我不知道。
-
也许你应该试试老派的
String.Format()看看是否有什么不同。我猜$"{key}"格式对于同一件事来说是句法糖果,但检查一下也无妨。 -
对不起,我忘了你提到它确实同步工作。尝试在下载方法中的代码周围设置访问锁。如果可行,请通过减少锁定来缩小范围。