【问题标题】:Parallel For causes massive lag spikes after 2,3 minutesParallel For 在 2.3 分钟后导致大量延迟峰值
【发布时间】:2016-12-28 06:17:48
【问题描述】:

编辑:
我注意到这些延迟峰值仅在 Visual Studio 中调试时发生。如果我在 Visual Stduio 之外运行 .exe,程序使用的 CPU 不会超过 3%。谁能告诉我为什么会这样?


我遇到了并行处理的问题。我正在使用 Parallel.For 检查大量代理(通过发出网络请求)。这是我的功能:

private ConcurrentBag<string> TotalProxies = new ConcurrentBag<string>();
private void CheckProxies()
{
    ParallelOptions pOptions = new ParallelOptions();
    pOptions.MaxDegreeOfParallelism = 100;
    int max = TotalProxies.Count;
    Invoke(new Action(() => { lbl_Status.Text = "Checking"; }));
    Parallel.For(0, max, pOptions, (index, loopstate) =>
    {
        string Proxy = TotalProxies.ElementAt(index);
        if (WebEngine.IsProxyWorking(Proxy))
        {
            WorkingProxies.Add(Proxy);
            workingp++;
            Invoke(new Action(() =>
            {
                lstv_Working.Items.Add(Proxy);
                lbl_Working.Text = workingp.ToString();
            }));
        }
        checkedp++;
        Invoke(new Action(() => { lbl_Checked.Text = checkedp.ToString(); }));

        if (Stop)
            loopstate.Stop();
    });
    Invoke(new Action(() => {
        lbl_Status.Text = "Idle";
    }));
}

我的问题如下:
该程序在前 0-2000 个请求中运行良好,其中 cpu 使用率约为 3-5%。然后,在 2-3 分钟后,我遇到了大量且频繁的延迟峰值,导致 CPU 使用率跃升至 100% .我不知道为什么会发生这种情况,因为它一直运行良好。我希望有人能帮助我了解导致这种情况的原因。
这里可以看到我的问题:

【问题讨论】:

  • 另外,这在几天前可以正常工作。现在我没有对代码进行任何更改就遇到了这个问题..
  • 您或许可以使用分析器来检查代码的哪一部分占用了 CPU。自从代码正常工作以来,您没有更改代码中的任何内容这一事实使我认为其中一个 HttpRequest 调用的行为可能与几天前的行为不同,这很可能是因为另一端的某些行为的行为不同(网站)。
  • @o_weisman 如何使用分析器?我使用了默认的诊断工具以及导致最多 cpu 使用的原因,它说的是外部代码。这是什么意思?
  • 另外,这方面(网站)没有问题,因为我尝试了不同的,但我有同样的问题
  • 首先是一个小旁注:++checkedp++ 不是原子的,在竞争条件下可能会给出错误的结果。使用Interlocked.Increment 更安全。也就是说,锁定会导致挂断,尽管我不知道它们是否会导致 CPU 峰值。但只要您调用而不是锁定变量本身,您就可以尝试在没有locks 的情况下运行。总而言之,由于等待的是 IO,所以最好使用async...await 构造。这也将消除对潜在大量线程的需求 (MaxDegreeOfParallelism)

标签: c# multithreading cpu-usage webrequest parallel.for


【解决方案1】:

正如承诺的 async/await 示例,虽然看到您的更新我不确定它是否会有所作为。但是由于它不适合评论,所以在这里发布;)

private ConcurrentBag<string> TotalProxies = new ConcurrentBag<string>();
private async Task CheckProxies()
{
    lbl_Status.Text = "Checking"; //NB, invoking is omitted assuming that CheckProxies is called from the UI thread itself
    var tasks = TotalProxies.Select(CheckProxy);
    await Task.WhenAll(tasks);
    lbl_Status.Text = "Idle";
}

private async Task<bool> CheckProxy(string p)
{   
    bool working = await Task.Run(() => WebEngine.IsProxyWorking(p)); //would be better if IsProxyWorking itself uses async methods and returns a task, so Task.Run isn't needed. Don't know if it's possible to alter that function?
    if(working)
    {
        WorkingProxies.Add(p);
        workingp++; //Interlocked.Increment is not necessary because after the await we're back in the main thread
        lstv_Working.Items.Add(p);  //are these items cleared on a new run? 
        lbl_Working.Text = workingp.ToString();
    }
    checkedp++;
    lbl_Checked.Text = checkedp.ToString(); 
    return working;
}

注意,由于我无法测试实际代码,我不确定效率。您当前的代码可能会执行得更好。但如果IsProxyWorking 方法可以使用实际的异步网络调用(我相信您的帖子中之前包含该代码),我相信处理确实可以改进。

【讨论】:

  • 如果你让CheckProxies 返回async Task 而不是async void,我会支持你。
  • @ScottChamberlain 这就是我的理由 ;) 更改了签名
  • 非常感谢您的帮助。在做了一些研究之后,似乎视觉工作室中出现了延迟峰值,因为一次抛出了大量异常(虽然不是 100% 肯定)。
【解决方案2】:

我不知道这是否与您的问题直接相关,但是将 MaxDegreeOfParallelism 设置为 100 并不好。您基本上是在告诉您的应用程序同时执行 100 个任务!根据MSDN

一般情况下,您不需要修改此设置。但是,您可以选择在高级使用场景中显式设置它,例如:

  • 当您知道您正在使用的特定算法不会扩展到超过一定数量的内核时。您可以设置该属性以避免在其他内核上浪费周期。

  • 当您同时运行多个算法并想要手动定义每个算法可以利用多少系统时。您可以为每个设置一个 P:System.Threading.Tasks.ParallelOptions.MaxDegreeOfParallelism 值。

  • 当线程池的启发式方法无法确定要使用的正确线程数并可能最终注入过多线程时。例如,在长时间运行的循环体迭代中,线程池可能无法区分合理进度或活锁或死锁,并且可能无法回收为提高性能而添加的线程。在这种情况下,您可以设置该属性以确保您使用的线程数不超过合理数量。

我会尝试删除此值并查看您的应用程序的行为!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-10-07
    • 1970-01-01
    • 2021-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-08
    • 1970-01-01
    相关资源
    最近更新 更多