处理大量异步作业

时间:2014-11-05 20:16:23

标签: c# multithreading asynchronous task

问题:我们有一些IP地址,然后我们应该按用户指定的时间段ping每一个,例如每400ms ping 192.168.0.1,每40000ms ping 192.168.137.20等等...我们可以以异步方式处理这种情况吗?

这是我从数据库加载目标信息并为每个人创建传感器的地方:

public class PingService
{
    private CancellationTokenSource _cancel;        
    private List<PingSensor> _pings;
    private IRepository<IPDevice> _deviceRepository;


    public PingService()
    {
        _cancel = new CancellationTokenSource();
        _pings = new List<PingSensor>();
        _deviceRepository = ObjectFactory.GetInstance<IRepository<IPDevice>>();
    }

    public void Start()
    {
        Action act = new Action(() =>
        {
            IQueryable<IPDevice> allDevices = _deviceRepository.GetAll();

            foreach (IPDevice device in allDevices)
            {
                PingResultCollector collector = new PingResultCollector(device);
                _pings.Add(new PingSensor(device.Address, collector, device.CheckDuration, _cancel.Token));
            }

            foreach (PingSensor _ping in _pings)
            {
                _ping.DoDiscovery();
            }
        });

        Task.Factory.StartNew(act);
    }

    public void Stop()
    {
        _cancel.Cancel();
    }
}

这是我们ping的地方,等待用户指定的延迟,收集响应和......

    public class PingSensor
{
    private string _address;        
    Ping _ping;
    private bool lastRequestReplayed;
    private int _delay;        
    private CancellationToken _cancellationToken;
    private PingResultCollector _resultCollector;


    public PingSensor(string address, PingResultCollector resultCollector, int delay, CancellationToken CancellationToken)
    {
        _address = address;
        _cancellationToken = CancellationToken;
        _ping = new Ping();
        _resultCollector = resultCollector;

        _ping.PingCompleted += _ping_PingCompleted;
        _delay = delay;
    }

    void _ping_PingCompleted(object sender, PingCompletedEventArgs e)
    {
        if (_cancellationToken.IsCancellationRequested)
            return;

        lastRequestReplayed = true;
        //_eventHandler(sender, e);

        if (_resultCollector != null)
            _resultCollector.CollectData(new PingStatus()
            {
                PingStatusId = Guid.NewGuid(),
                Address = e.Reply == null ? "" : e.Reply.Address.ToString(),
                Status = e.Reply == null ? e.Error.Message : e.Reply.Status.ToString(),
                Target = e.UserState.ToString(),
                ResponseTime = e.Reply == null ? 0 : e.Reply.RoundtripTime,
                UpdateTime = DateTime.UtcNow
            });
    }

    public void DoDiscovery()
    {
        lastRequestReplayed = true;
        Action act = new Action(() =>
        {
            while (_cancellationToken.IsCancellationRequested!=true)
            {
                if (_cancellationToken.IsCancellationRequested)
                    return;

                byte[] data = new byte[2];
                if (lastRequestReplayed)
                {
                    _ping.SendAsync(_address, 30000, data, _address);
                    lastRequestReplayed = false;
                }
                if (_cancellationToken.IsCancellationRequested)
                    return;
                Thread.Sleep(_delay);
                if (_cancellationToken.IsCancellationRequested)
                    return;
            }
        });

        Task.Factory.StartNew(act);
    }
}

这个代码的问题是它为每个传感器创建一个线程,这意味着如果我有500个目标来ping我也有500个线程!有什么建议吗?抱歉我的英语不好:D

2 个答案:

答案 0 :(得分:1)

我可以用工作代码示例来演示这个概念..由你来转换成适合你的场景的代码100%

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.NetworkInformation;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            // Pick your long list of ip address
            List<IPAddress> ips = new List<IPAddress>
            {
                new IPAddress(new byte[] {127, 0, 0, 1}),
                new IPAddress(new byte[] {198, 252, 206, 16}),
                new IPAddress(new byte[] {74, 125, 129, 99}),
                // Add more ips as you like
            };

            // Exactly what do you do with initiated tasks will depend on your specific scenario.
            List<Task> tps = new List<Task>();

            foreach(var ip in ips)
            {
                // Delay could vary by IP, but I am hardcoding 10s here.
                tps.Add(InitiatePings(ip, 10000));
            }

            // Needed so that console app doesn't exit..
            Console.ReadLine();
        }

        private static async Task InitiatePings(IPAddress ip, int delay)
        {
            while (true)
            {
                // Note, this API is different from SendAsync API you are using
                // You may also want to reuse Ping instance instead of creating new one each time.
                var result = await new Ping().SendPingAsync(ip);
                // Process your result here, however you want.
                Console.WriteLine(result.Address + "-" + result.Status + "-" + result.RoundtripTime);
                // Assumes that the delay is not absolute, but time between ping, result processing and next ping.
                await Task.Delay(delay);
            }
        }
    }
}

答案 1 :(得分:1)

为什么不为每个要ping的IP地址设置Timer?那就是:

List<System.Threading.Timer> timers = new List<System.Threading.Timer>();

您可以使用以下内容添加各个计时器:

timers.Add(new Timer(PingTimerProc, "192.168.1.1", 400, 400));
timers.Add(new Timer(PingTimerProc, "10.10.200.50", 42000, 42000));
// add other timers

你的计时器proc:

void PingTimerProc(object state)
{
    string ipAddress = (string)state;
    // do the ping here
}

这里唯一的潜在缺点是,如果需要非常频繁地ping通足够多的IP地址,则存在无法足够快地处理所有计时器事件的风险。这最终会导致颠簸,因为你会有一堆待定的线程。

如果你用一个线程执行此操作并且在如此短的时间间隔内有很多ping,那么你就会开始落后。它不会最终杀死你的过程。

如果我使用单个线程执行此操作,我将创建IP地址的优先级队列及其关联的ping时间。并且长时间运行的任务设置为该队列提供服务。基本结构将是这样的:

class PingJob
{
    string IPAddress;
    int Period;  // milliseconds between pings
    TimeSpan NextPingTime;
}

var queue = new PriorityQueue<PingJob>();

// create the ping jobs and add them to the queue
queue.Add(new PingJob { IPAddress = "192.168.1.1", Period = 400, NextPingTime = TimeSpan.FromMilliseconds(400) });
queue.Add(new PingJob { IPAddress = "10.10.200.50", Period = 42000, NextPingTime = TimeSpan.FromMilliseconds(42000) });
// etc.

// start a thread that processes the pings
var pingTask = Task.Factory.StartNew(PingTaskProc, TaskCreationOptions.LongRunning);

任务处理:

void PingTaskProc()
{
    var pingTimer = Stopwatch.StartNew();
    while (true)
    {
        var p = queue.Dequeue(); // get ping job
        if (p.NextPingTime > pingTimer.Elapsed)
        {
            // wait for ping time to come up
            Thread.Sleep(p.NextPingTime - pingTimer.Elapsed);
        }
        // ping the IP address
        DoPing(p.IPAddress);
        // update its next ping time
        p.NextPingTime += TimeSpan.FromMilliseconds(p.Period);
        // And add it back to the queue
        queue.Add(p);
    }
}

假设您具有可用的通用优先级队列数据结构。有许多可用的,包括mine

如果您可以从队列中添加和删除IP地址,或更改其ping周期,那么事情会变得更有趣。但你没有指明这一点。