c# - 在 Foreach 中创建和启动任务

标签 c# closures task

我试图从网站上抓取一些数据。这是我的类(class):

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 位置的关联很好。

我想这可能是一个关闭问题,记忆起 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在改变。我很确定代码是线程安全的,没有共享状态,对吧?

但显然不是,因为当我同步遍历字典时,一切都按预期工作。

也许我在这里遗漏了一些关于外壳、线程、内存或其他方面的基本要点。我不知道,但我的办公 table 上满是头发,而且我快要秃顶了。

最佳答案

任务并行库有一个 for each 方法,非常适合您正在做的事情。您可能会发现这与您当前正在尝试做的事情很有趣/相关:

https://msdn.microsoft.com/en-us/library/dd460720(v=vs.110).aspx

关于c# - 在 Foreach 中创建和启动任务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34536679/

相关文章:

c# - 将 Angular 4 添加到 ASP.NET Core 项目

c# - 快速返回所有小于或可被七整除的数字

c# - 从带参数的电子邮件启动 ClickOnce 应用程序

grails - 从HTTPResponse闭合Grails HTTPBuilder返回值到外部方法

c# - CancellationTokenSource 与 volatile bool 值

c# - 如何使用 asp.net 检查在 TreeView 子节点中导航页面的权限?

Swift 可选转义闭包参数

functional-programming - 函数式编程 : Are maps sequential? 对闭包的影响

c# - Task.WaitAny 当有多个任务同时完成时

multithreading - .net 4.0 任务 : Synchronize on one or more objects