C# 在Foreach中创建和启动任务
我试图从一个网站上搜集一些数据。这是我的班级:C# 在Foreach中创建和启动任务,c#,closures,task,C#,Closures,Task,我试图从一个网站上搜集一些数据。这是我的班级: class ClosureCraziness { public string SaveFolder { get; set; } public void Save(Dictionary<string, string> idToWebLocation) { var tasks = new List<Task>(); foreach (var kvp in idToWebL
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);
}
}
}
为了安全起见:
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保存(字典idToWebLocation)
{
var tasks=新列表();
foreach(idToWebLocation中的var kvp)
{
var innervp=kvp;
var id=innerKvp.Key;
var loc=内部KVP.值;
var task=新任务(()=>下载(id,loc));
task.Start();
任务。添加(任务);
}
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());
}
public void保存(字典idToWebLocation)
{
var tasks=新列表();
foreach(idToWebLocation中的var kvp)
{
var innervp=kvp;
变量任务=新任务(()=>
{
var id=innerKvp.Key;
var loc=内部KVP.值;
下载(id,loc);
});
task.Start();
任务。添加(任务);
}
Task.WaitAll(tasks.ToArray());
}
但这两种方法都不奏效。显然,我对这段代码是如何编译的缺乏理解,但我的意思是,到底发生了什么
它似乎介于var filename=$“{id}.html”之间代码>和source=GetSource(位置)代码>位置正在更改。我很确定代码是线程安全的,没有共享状态,对吗
但显然不是,因为当我同步遍历字典时,一切都完全按照预期进行
也许我在这里遗漏了一些基本点,关于外壳、线程、内存等等。我不知道,但我的书桌上满是头发,我快要秃顶了 我认为在创建任务之前,应该尝试创建key和value的局部变量
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);
}
}
}
类封闭性
{
公共字符串保存文件夹{get;set;}
公共作废保存(字典idToWebLocation)
{
var tasks=新列表();
foreach(idToWebLocation中的var kvp)
{
添加(下载(kvp.Key,kvp.Value));
}
Task.WaitAll(tasks.ToArray());
}
异步任务下载(字符串id、字符串位置)
{
var filename=$“{id}.html”;
string source=string.Empty;
尝试
{
source=等待获取源(位置);
}
捕获(例外e)
{
filename=“e-”+文件名;
var ex=e;
while(ex!=null)
{
来源+=例如消息;
source+=Environment.NewLine;
source+=Environment.NewLine;
source+=ex.StackTrace;
ex=ex.InnerException;
}
}
var path=path.Combine(保存文件夹,文件名);
使用(var sw=新StreamWriter(路径))
wait sw.WriteAsync(源);
}
异步任务GetSource(字符串位置)
{
使用(var client=new WebClient())
{
返回wait client.downloadstringtasksync(位置);
}
}
}
与您原来的任务相比,我所做的唯一更改是使用任务
返回Write
和DownloadString
的版本,将您的助手方法更改为返回任务
s,并添加一些异步
s和等待
s以使代码编译。我手头没有编译器,但这应该非常接近正确
实际上,我并不认为您的原始版本存在问题,但通过将任务创建封装在一个以其输入为参数的函数中,我们应该能够最大限度地减少与闭包相关的bug潜入的可能性。如果您怀疑字典存在线程问题(我认为也不是,它不参与并行执行),试试ConcurrentDictionary。好好看看源文件,它们真的有你期望的内容吗?@MartinMaat它们没有我期望的内容,这就是问题所在。除非你是说当我运行同步时,在这种情况下,是的,我仔细检查了它们。而且-我无法想象字典是怎么回事。这是C#6($“您可以在这里看到使用的字符串{interpolation}”-这是惊人的)根据.NET 4.5.2编译。但我真的认为这不再是一个闭包问题。也许。我不知道。也许您应该试试老式的string.Format()
,看看这是否有区别。我猜$“{key}”
格式对于同样的东西来说是一种语法糖果,但检查也无妨。很抱歉,我忘了你提到它是同步工作的。尝试在下载方法中对代码设置访问锁。如果它有效,请通过减少锁定来缩小它的范围。除非我遗漏了什么,否则字典只在一个线程上使用。你知道吗w什么?你是对的。也就是说,假设你没有在Save()工作流之外的另一个线程中修改该字典(修改后的闭包是错误的)。我将更改我的答案以删除并发字典,但你可能希望检查每个分区工作流的TPL。它是为此类问题而设计的。
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);
}
}
}