TPL与多线程

时间:2014-11-03 08:36:52

标签: c# multithreading task-parallel-library

我是线程新手,我需要澄清以下情况。

我正在研究苹果推送通知服务。我的应用程序要求在向网站添加新交易时向30k用户发送通知。

我可以将30k用户拆分成列表,每个列表包含1000个用户并启动多个线程或者可以使用任务吗?

以下方式有效吗?

if (lstDevice.Count > 0)
{

    for (int i = 0; i < lstDevice.Count; i += 2)
    {
        splitList.Add(lstDevice.Skip(i).Take(2).ToList<DeviceHelper>());
    }

    var tasks = new Task[splitList.Count];
    int count=0;
    foreach (List<DeviceHelper> lst in splitList)
    {
        tasks[count] = Task.Factory.StartNew(() =>
        {
            QueueNotifications(lst, pMessage, pSubject, pNotificationType, push);
        },
            TaskCreationOptions.None);
       count++;
    }

QueueNotification方法将遍历每个列表项并创建一个有效负载,如

foreach (DeviceHelper device in splitList)
{
    if (device.PlatformType.ToLower() == "ios")
    {
        push.QueueNotification(new AppleNotification()
                                    .ForDeviceToken(device.DeviceToken)
                                    .WithAlert(pMessage)
                                    .WithBadge(device.Badge)
                                     );
        Console.Write("Waiting for Queue to Finish...");
    }
}
push.StopAllServices(true);

4 个答案:

答案 0 :(得分:5)

从技术上讲,确定可以拆分列表,然后启动并行运行List的线程。您也可以像以前一样自己实现所有内容,但这不是一个好方法。首先将List拆分为并行处理的块已经是Parallel.ForParallel.ForEach所做的事情。没有必要自己重新实现一切。

现在,您不断询问是否可以并行运行300或500个通知。但实际上这不是一个好问题,因为你完全错过了并行运行的重点。

所以,让我解释一下为什么这个问题不好。首先,你应该问自己为什么要并行运行?答案是,您希望通过使用多个CPU核心来运行更快的东西。

现在你的简单想法可能是产生300或500个线程的速度更快,因为你有更多线程并且它“并行”运行更多东西。但事实并非如此。

首先,创建一个线程不是“免费”的。您创建的每个线程都有一些开销,创建一个线程需要一些CPU时间,而且还需要一些内存。最重要的是,如果你创建300个线程,它并不意味着并行运行300个线程。如果你有一个8核CPU,那么只有8个线程可以并行运行。创建更多线程甚至可能会损害您的性能。因为现在你的程序需要在线程之间切换constanlty,这也会降低CPU的性能。

所有这一切的结果。如果你有轻量级的一些小代码,它们不会进行大量计算,那么它会导致创建大量线程会降低应用程序速度而不是更快地运行,因为管理线程会产生比运行它更多的开销(对于例子)8个cpu-cores。

这意味着,如果你有一个30,000的列表。它通常会以更快的方式将您的列表拆分为8个块,并在8个线程中通过您的列表来创建300个线程。

你的目标永远不应该是:它能并行运行xxx吗? 问题应该是:我需要多少线程,每个线程应该处理多少项目以使我的工作最快完成。

这是一个重要的区别,因为只是产生更多的线程并不意味着某些东西最终会成功。

那么你需要多少线程,每个线程应该处理多少项?好吧,你可以编写很多代码来测试它。但是数量从硬件变为硬件。只有4个核心的PC比具有8个核心的系统具有另一个优化。如果你正在做的是IO绑定(例如读/写到磁盘/网络),你也不会通过增加线程来获得更快的速度。

所以你现在可以做的就是测试一切,尝试获得正确的线程编号并进行大量的基准测试以找到最佳数字。

但实际上,这就是具有Task<T>类的TPL库的全部目的。 Task<T>类已经在您的计算机上查看它拥有多少个cpu核心。当您运行任务时,它会自动尝试创建所需的线程,以便最大限度地利用您的系统。

所以我的建议是你应该使用带有Task<T>类的TPL库。在我看来,你不应该自己直接创建线程或自己做分区,因为所有这些都已在TPL中完成。

答案 1 :(得分:0)

我认为Task - Class对你的目标来说是一个很好的选择,因为你可以轻松处理异步过程并且不必直接处理Threads。

也许这有帮助:Task vs Thread differences

但为了给你一个更好的答案,你应该改进你的问题,给我们更多细节。

创建多个并行线程时应该小心,因为这会降低应用程序的速度。阅读SO中的这篇好文章:How many threads is too many?。最好的是你可以配置它,而不是测试一些值。

答案 2 :(得分:0)

我同意Task是一个不错的选择,但创建太多任务也会给您的系统和失败带来风险,您的决定也是提出解决方案的一个因素。对我来说,我更喜欢MSQueue与线程池相结合。

答案 3 :(得分:0)

如果您希望并行创建推送通知并使用计算机上的所有CPU来最大限度地提高性能,则应使用Parallel.ForEach

Parallel.ForEach(
  devices,
  device => {
    if (device.PlatformType.ToUpperInvariant() == "IOS") {
      push.QueueNotification(
        new AppleNotification()
          .ForDeviceToken(device.DeviceToken)
          .WithAlert(message)
          .WithBadge(device.Badge)
      );
    }
  }
);
push.StopAllServices(true);

这假定调用push.QueueNotification是线程安全的。此外,如果此调用锁定共享资源,则由于锁争用,您可能会看到低于预期的性能。

为避免此锁定争用,您可以为Parallel.ForEach创建的每个分区创建单独的队列。我在这里即兴创作,因为问题中缺少一些细节。我假设变量pushPush类型的实例:

Parallel.ForEach(
  devices,
  () => new Push(),
  (device, _, push) => {
    if (device.PlatformType.ToUpperInvariant() == "IOS") {
      push.QueueNotification(
        new AppleNotification()
          .ForDeviceToken(device.DeviceToken)
          .WithAlert(message)
          .WithBadge(device.Badge)
      );
    }
    return push;
  },
  push.StopAllServices(true);
);

这将为Push创建的每个分区创建一个单独的Parallel.ForEach实例,当分区完成时,它将在实例上调用StopAllServices

这种方法应该不会比将设备分成N个列表更糟糕,其中N是CPU的数量,并且开始N个线程或N个任务来处理每个列表。如果一个线程或任务&#34;落后&#34;总执行时间将是这个&#34;慢&#34;的执行时间。线程或任务。使用Parallel.ForEach,所有CPU都被使用,直到所有设备都被处理完毕。