在Foreach中创建和启动任务
本文关键字:启动 任务 创建 Foreach | 更新日期: 2023-09-27 18:28:35
我试图从一个站点中抓取一些数据。这是我的课:
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>
葡萄.html <html> apple </html>
plum.html <html> plum </html>
(我不知道如何很好地格式化这个)
起初,我很困惑,因为磁盘上的文件名是正确的,而且我确信我的Dictionary<string, string>
格式正确(我检查了6次,所有不同的方式),这意味着Id与web位置的关联很好。
我想这可能是一个结束问题,回忆起埃里克·利珀特教我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
正在发生变化。我确信代码是线程安全的,没有共享状态,对吧?
但显然不是,因为当我同步地遍历字典时,一切都和预期的一样。
也许我在这里遗漏了一些基本点,关于外壳、线程或内存之类的。我不知道,但我的桌子上满是头发,我快要秃顶了。
任务并行库有一个for each方法,它非常适合您正在做的事情。你可能会发现这与你目前正在尝试做的事情很有趣/相关:
https://msdn.microsoft.com/en-us/library/dd460720(v=vs.110).aspx
我认为在创建任务之前,应该尝试创建键和值的局部变量。
var key = kvp.Key;
var value = kvp.Value;
var task = new Task(() => Download(key, value));
如果您将代码更改为使用async/await而不是自己构建任务,您还会遇到问题吗?
class ClosureCraziness
{
public string SaveFolder { get; set; }
public void Save(Dictionary<string, string> idToWebLocation)
{
var tasks = new List<Task>();
foreach (var kvp in idToWebLocation)
{
tasks.Add(Download(kvp.Key, kvp.Value));
}
Task.WaitAll(tasks.ToArray());
}
async Task Download(string id, string location)
{
var filename = $"{id}.html";
string source = string.Empty;
try
{
source = await GetSource(location);
}
catch (Exception e)
{
filename = "e-" + filename;
var ex = e;
while (ex != null)
{
source += ex.Message;
source += Environment.NewLine;
source += Environment.NewLine;
source += ex.StackTrace;
ex = ex.InnerException;
}
}
var path = Path.Combine(SaveFolder, filename);
using (var sw = new StreamWriter(path))
await sw.WriteAsync(source);
}
async Task<String> GetSource(string location)
{
using (var client = new WebClient())
{
return await client.DownloadStringTaskAsync(location);
}
}
}
与您原来的版本相比,我只更改了Task
返回版本的Write
和DownloadString
,更改您的helper方法以返回Task
s,并在一些async
s和await
s中添加胡椒粉以编译代码。我手头没有编译器,但这应该很接近正确。
我实际上没有看到你的原始版本有什么问题,但通过将任务创建封装在一个以其输入为参数的函数中,我们应该能够最大限度地减少与关闭相关的错误悄悄出现的可能性。