【问题标题】:C# - Problems with task resultsC# - 任务结果问题
【发布时间】:2020-04-06 14:36:45
【问题描述】:

我必须通过字符串比较在 758.000 个地址的列表中找到重复的地址。 到目前为止我做了什么:

public int StartDuplicateFinder(List<Address> addresses, List<Address> checklist)
{
    int found = 0;

    foreach(Address addr1 in addresses)
    {
        List<Address> addresses2 = checklist.FindAll(
        delegate (Address addr2)
        {
            return addr2.AddressString == addr1.AddressString && addr2.Duplicate == "" 
                && addr2.AddrIndex > addr1.AddrIndex;
        }
        );

        foreach(Address addr2 in addresses2)
        {
            addr2.Duplicate = "1";
            found++;
        }
    }
    return found;
}

这需要大约 7 个小时(太长),并提供了大约 93.000 个副本!

为了加快速度,我将checklist 拆分为List&lt;Address&gt; checklists 4 个部分(200k、200k、200k 和 158k)并像这样使用Task

public class Worker
{
    private List<Address> addresses = null;
    private List<Addresses> checklist = null
    private int found = 0;

    public Worker(List<Address> _addresses, List<Address> _checklist)
    {
        addresses = _addresses; //always 758.000 addresses

        //with 4 Tasks: Task 1, 2 and 3 = 200.000 addresses, Task 4 = 158.000 addresses
        //with 6 Tasks: Task 1, 2, 3, 4 and 5 with 150.000 addresses, Task 6 with 8.000 addresses
        checklist = _checklist;
    }

    public int StartDuplicateFinder()
    {
        foreach(Address addr1 in addresses)
        {
            List<Address> addresses2 = checklist.FindAll(
            delegate (Address addr2)
            {
                return addr2.AddressString == addr1.AddressString && addr2.Duplicate == "" 
                    && addr2.AddrIndex > addr1.AddrIndex;
            }
            );

            foreach(Address addr2 in addresses2)
            {
                addr2.Duplicate = "1";
                found++;
            }
        }
    }

    public int Found {get {return found;}}
}

private async void StartTasks(List<Task> Tasklist)
{
    foreach (Task t in Tasklist)
    {
        t.Start();
    }
    await Task.WhenAll(Tasklist.ToArray());
}

private void DoSomething()
{
    List<Task> Tasklist = new List<Task>();

    foreach(List<Address> checklist in checklists)
    {
        Worker w = new Worker(addresses, checklist);
        Tasklist.Add(new Task(StartDuplicateFinder));        
    }
    StartTasks(Tasklist);

    //wait until the tasks are finished
    //do other stuff
    ...
}

现在它只运行 35 分钟!但是,如果我查看发现的重复项,则存在巨大的偏差。只找到了大约 27.000 个重复项。

我试了几次,每次都得到其他结果。

4 Tasks, first run:     4 Tasks, second run:
Task# > duplicates      Task# > duplicates
1     >   749           1     >   689
2     >  2450           2     >  2391
3     > 10304           3     > 10073
4     > 14462           4     > 14282
Sum   > 27965           Sum   > 27435

6 Tasks, first run:     6 Tasks, second run:
Task# > duplicates      Task# > duplicates
1     >    16           1     >    24
2     >    56           2     >    55
3     >   202           3     >   236
4     >   679           4     >   634
5     >   852           5     >   800
6     >  2985           6     >  2981
Sum   >  4790           Sum   >  4730

每次都是相同的 758.000 个地址列表。

我用TaskThreadBackgroundWorker 尝试过,但我总是得到不同的结果! 如果我在 1 个任务中运行它,结果总是 92.377 个重复项(我认为这是正确的)。

谁能帮我解决这个问题?

【问题讨论】:

  • 请发布您构建和执行的代码。

标签: c# winforms multitasking


【解决方案1】:

问题中没有足够的代码来推测。如果你真的想排除故障,你需要一个完整的、独立的 repo。但这种行为的典型原因是您正在多个线程中修改共享数据。根据经验,并行工作者应该对只读数据进行操作,返回它们的结果以由主线程累积。

但要考虑使用更高效的数据结构和算法,而不是并行处理。 IE 代替嵌套循环连接,构建一个哈希表(Dictionary&lt;TKey,TValue&gt;Lookup&lt;TKey,TValue&gt;)以在单个线程上更快(至少作为第一步)。例如:

public int StartDuplicateFinder(List<Address> addresses, List<Address> checklist)
{
    int found = 0;

    var checklistByAddressString = checklist.ToLookup(a => a.AddressString, a => a);

    foreach (Address addr1 in addresses)
    {
        var addressMatches = checklistByAddressString[addr1.AddressString];
        var addresses2 = addressMatches.Where(addr2 => addr2.Duplicate == ""
                && addr2.AddrIndex > addr1.AddrIndex);

        foreach (Address addr2 in addresses2)
        {
            addr2.Duplicate = "1";
            found++;
        }
    }
    return found;
}

【讨论】:

    【解决方案2】:

    问题是您在循环访问 addresses 列表中的项目时正在修改它们。您正在创造竞争条件。

    作为标准的一部分,您的过滤器具有:

    addr2.Duplicate == ""
    

    稍后您更改项目:

    addr2.Duplicate = "1"`.
    

    也许你将addresses分块并反对完整的checkList会更好

    或者,只需在单个线程上针对两个列表使用LINQ query。与手动遍历集合相比,您很可能会获得更快的结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-25
      • 2020-01-26
      • 2019-04-13
      • 2019-02-24
      • 2019-10-07
      • 1970-01-01
      相关资源
      最近更新 更多