有效地使用数千个任务和超时

时间:2015-08-27 13:53:09

标签: c# sockets parallel-processing task

我正在实现一个通过套接字与另一个应用程序A进行通信的库L.

基本工作流程如下:

  1. L连接到A.
  2. L向A发送~50,000条信息I. 为每个发出的I创建一个任务T.
  3. L侦听来自A的传入结果,一旦重新存在,就使用a TaskCompletionSource用于设置Tasks T
  4. 的结果
  5. L创建一个具有设置超时的任务T2(Task.WhenAny(T,Task.Delay(xx))
  6. L使用Task.WhenAll(T2)等待超时或结果发送所有已发送的信息。
  7. 管理底层数据结构完全没问题。主要的问题是组装“主”任务。当所有(T2)花费大约5-6秒在我的计算机上用大约。 50.000个条目(创建50.000 * 2 + 1个任务)。

    然而,我无法想象一个更轻巧的方式来实现同样的目标。它应该使用所有可用的核心并且是非阻塞的,并且支持超时。

    有没有办法使用Parallel-或ThreadPool类来提高性能?

    编辑: 显示基本设置如何的代码: https://dotnetfiddle.net/gIq2DP

2 个答案:

答案 0 :(得分:2)

开始总计 n LongRunningTasks,其中 n 是计算机上的核心数。每个任务都应该在一个核心上运行。为每个要发送的I创建50K新任务将是一种浪费。而是设计接受 I和套接字信息的任务 - 发送此信息的位置

创建BlockingCollection<Tuple<I, SocketInfo>>。启动一个任务以填充此阻止集合。您之前创建的其他n个长时间运行的任务可以继续使用信息元组和地址来发送信息,然后在阻塞收集完成时中断的循环中为您执行作业。

可以在长时间运行的任务中设置超时。

这整个设置将使您的CPU忙于有用的工作,而不是让它不必要地忙着“创建 50K任务”的“工作”。

由于发生在主内存之外的操作(如此网络操作)为very very slow,因此可以随意设置 n ,而不仅仅等于机器中的内核数量但即使是三倍的价值。在我的代码演示中,我将其设置为仅等于核心数。

使用所提供链接的代码,这是一种方式......

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Net.NetworkInformation;
using System.Threading.Tasks;

namespace TestConsoleApplication
{
    public static class Test
    {
        public static void Main()
        {
            TaskRunningTest();
        }

        private static void TaskRunningTest()
        {
            var s = new Stopwatch();
            const int totalInformationChunks = 50000;
            var baseProcessorTaskArray = new Task[Environment.ProcessorCount];
            var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);
            var tcs = new TaskCompletionSource<int>();

            var itemsToProcess = new BlockingCollection<Tuple<Information, Address>>(totalInformationChunks);

            s.Start();
            //Start a new task to populate the "itemsToProcess"
            taskFactory.StartNew(() =>
            {
                // Add Tuples of Information and Address to which this information is to be sent to.
                Console.WriteLine("Done intializing all the jobs...");
                // Finally signal that you are done by saying..
                itemsToProcess.CompleteAdding();
            });

            //Initializing the base tasks
            for (var index = 0; index < baseProcessorTaskArray.Length; index++)
            {
                var thisIndex = index;
                baseProcessorTaskArray[index] = taskFactory.StartNew(() =>
                {
                    while (!itemsToProcess.IsAddingCompleted && itemsToProcess.Count != 0)
                    {
                        Tuple<Information, Address> item;
                        itemsToProcess.TryTake(out item);
                        //Process the item
                        tcs.TrySetResult(thisIndex);
                    }
                });
            }

            // Need to provide new timeout logic now
            // Depending upon what you are trying to achieve with timeout, you can devise out the way

            // Wait for the base tasks to completely empty OR
            // timeout and then stop the stopwatch.
            Task.WaitAll(baseProcessorTaskArray); 
            s.Stop();
            Console.WriteLine(s.ElapsedMilliseconds);
        }

        private class Address
        {
            //This class should have the socket information
        }

        private class Information
        {
            //This class will have the Information to send
        }
    }
}

答案 1 :(得分:1)

分析表明大多数时间(90%?)花费在计时器设置,到期和处理上。这对我来说似乎有道理。

也许你可以创建自己的超便宜超时机制。将超时排入按到期时间排序的优先级队列。然后,每隔100ms运行一个计时器,并使该计时器到期的优先级队列中的所有内容。

这样做的成本是每个超时一TaskCompletionSource和一些小的进一步处理。

您甚至可以通过从队列中删除超时来取消超时,只需删除TaskCompletionSource