简体   繁体   中英

Parallel processing using TPL in windows service

I have a windows service which is consuming a messaging system to fetch messages. I have also created a callback mechanism with the help of Timer class which helps me to check the message after some fixed time to fetch and process. Previously, the service is processing the message one by one. But I want after the message arrives the processing mechanism to execute in parallel. So if the first message arrived it should go for processing on one task and even if the processing is not finished for the first message still after the interval time configured using the callback method (callback is working now) next message should be picked and processed on a different task.

Below is my code:

Task.Factory.StartNew(() =>
{
    Subsriber<Message> subsriber = new Subsriber<Message>()
    {
       Interval = 1000
    };

    subsriber.Callback(Process, m => m != null);
});

public static void Process(Message message)
{
  if (message != null)
  {
     // Processing logic
  }
 else
 {

 }
}

But using the Task Factory I am not able to control the number of tasks in parallel so in my case I want to configure the number of tasks on which messages will run on the availability of the tasks?


Update: Updated my above code to add multiple tasks

Below is the code:

         private static void Main()
        {
            try
            {
                int taskCount = 5;
                Task.Factory.StartNewAsync(() =>
                {
                   Subscriber<Message> consumer = new 
                   Subcriber<Message>()
                   {
                       Interval = 1000
                    };

                   consumer.CallBack(Process, msg => msg!= 
                   null);
                 }, taskCount);
                Console.ReadLine();
              }
             catch (Exception e)
            {
                 Console.WriteLine(e.Message);
            }

            public static void StartNewAsync(this TaskFactory 
            target, Action action, int taskCount)
           {
                 var tasks = new Task[taskCount];
                 for (int i = 0; i < taskCount; i++)
                 {
                      tasks[i] = target.StartNew(action);
                 }
             }

             public static void Process(Message message)
            {
                 if (message != null)
                {

                 }
                else
                { }
             }
        }

I think what your looking for will result in quite a large sample. I'm trying just to demonstrate how you would do this with ActionBlock<T> . There's still a lot of unknowns so I left the sample as skeleton you can build off. In the sample the ActionBlock will handle and process in parallel all your messages as they're received from your messaging system

public class Processor
{
    private readonly IMessagingSystem _messagingSystem;
    private readonly ActionBlock<Message> _handler;
    private bool _pollForMessages;

    public Processor(IMessagingSystem messagingSystem)
    {
        _messagingSystem = messagingSystem;
        _handler = new ActionBlock<Message>(msg => Process(msg), new ExecutionDataflowBlockOptions()
        {
            MaxDegreeOfParallelism = 5 //or any configured value
        });
    }

    public async Task Start()
    {
        _pollForMessages = true;
        while (_pollForMessages)
        {
            var msg = await _messagingSystem.ReceiveMessageAsync();
            await _handler.SendAsync(msg);
        }

    }

    public void Stop()
    {
        _pollForMessages = false;
    }

    private void Process(Message message)
    {
        //handle message
    }
}

More Examples

And Ideas

Ok, sorry I'm short on time but here's the general idea/skeleton of what I was thinking as an alternative.

If I'm honest though I think the ActionBlock<T> is the better option as there's just so much done for you, with the only limit being that you can't dynamically scale the amount of work it will do it once, although I think the limit can be quite high. If you get into doing it this way you could have more control or just have a kind of dynamic amount of tasks running but you'll have to do a lot of things manually, eg if you want to limit the amount of tasks running at a time, you'd have to implement a queueing system (something ActionBlock handles for you) and then maintain it. I guess it depends on how many messages you're receiving and how fast your process handles them.

You'll have to check it out and think of how it could apply to your direct use case as I think some of the details area a little sketchily implemented on my side around the concurrentbag idea.

So the idea behind what I've thrown together here is that you can start any number of tasks, or add to the tasks running or cancel tasks individually by using the collection.

The main thing I think is just making the method that the Callback runs fire off a thread that does the work, instead of subscribing within a separate thread.

I used Task.Factory.StartNew as you did, but stored the returned Task object in an object ( TaskInfo ) which also had it's CancellationTokenSource, it's Id (assigned externally) as properties, and then added that to a collection of TaskInfo which is a property on the class this is all a part of:

Updated - to avoid this being too confusing i've just updated the code that was here previously.

You'll have to update bits of it and fill in the blanks in places like with whatever you have for my HeartbeatController , and the few events that get called because they're beyond the scope of the question but the idea would be the same.

    public class TaskContainer
{
    private ConcurrentBag<TaskInfo> Tasks;
    public TaskContainer(){
        Tasks = new ConcurrentBag<TaskInfo>();
    }


//entry point
//UPDATED
public void StartAndMonitor(int processorCount)
{

        for (int i = 0; i <= processorCount; i++)
        {
            Processor task = new Processor(ProcessorId = i);
            CreateProcessorTask(task);
        }
        this.IsRunning = true;
        MonitorTasks();
}

private void CreateProcessorTask(Processor processor)
{
    CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

    Task taskInstance = Task.Factory.StartNew(
        () => processor.Start(cancellationTokenSource.Token)
    );
    //bind status update event
    processor.ProcessorStatusUpdated += ReportProcessorProcess;

    Tasks.Add(new ProcessorInfo()
    {
        ProcessorId = processor.ProcessorId,
        Task = taskInstance,
        CancellationTokenSource = cancellationTokenSource
    });

}

//this method gets called once but the HeartbeatController gets an action as a param that it then
//executes on a timer. I haven't included that but you get the idea

//This method also checks for tasks that have stopped and restarts them if the manifest call says they should be running.
//Will also start any new tasks included in the manifest and stop any that aren't included in the manifest.
internal void MonitorTasks()
    {
        HeartbeatController.Beat(() =>
        {
            HeartBeatHappened?.Invoke(this, null);
            List<int> tasksToStart = new List<int>();

            //this is an api call or whatever drives your config that says what tasks must be running.
            var newManifest = this.GetManifest(Properties.Settings.Default.ResourceId);

            //task Removed Check - If a Processor is removed from the task pool, cancel it if running and remove it from the Tasks List.
            List<int> instanceIds = new List<int>();
            newManifest.Processors.ForEach(x => instanceIds.Add(x.ProcessorId));
            var removed = Tasks.Select(x => x.ProcessorId).ToList().Except(instanceIds).ToList();

            if (removed.Count() > 0)
            {
                foreach (var extaskId in removed)
                {
                    var task = Tasks.FirstOrDefault(x => x.ProcessorId == extaskId);
                    task.CancellationTokenSource?.Cancel();
                }
            }

            foreach (var newtask in newManifest.Processors)
            {
                var oldtask = Tasks.FirstOrDefault(x => x.ProcessorId == newtask.ProcessorId);
                //Existing task check
                if (oldtask != null && oldtask.Task != null)
                {
                    if (!oldtask.Task.IsCanceled && (oldtask.Task.IsCompleted || oldtask.Task.IsFaulted))
                    {
                        var ex = oldtask.Task.Exception;

                        tasksToStart.Add(oldtask.ProcessorId);
                        continue;
                    }
                }
                else //New task Check                       
                    tasksToStart.Add(newtask.ProcessorId);


            }

            foreach (var item in tasksToStart)
            {
                var taskToRemove = Tasks.FirstOrDefault(x => x.ProcessorId == item);
                if (taskToRemove != null)
                    Tasks.Remove(taskToRemove);

                var task = newManifest.Processors.FirstOrDefault(x => x.ProcessorId == item);
                if (task != null)
                {
                    CreateProcessorTask(task);
                }
            }
        });
    }

}

//UPDATED
public class Processor{

private int ProcessorId;
private Subsriber<Message> subsriber;

public Processor(int processorId) => ProcessorId = processorId;

public void Start(CancellationToken token)
{
    Subsriber<Message> subsriber = new Subsriber<Message>()
    {
        Interval = 1000
    };

    subsriber.Callback(Process, m => m != null);
}

private void Process()
{
    //do work
}
}

Hope this gives you an idea of how else you can approach your problem and that I didn't miss the point :).


Update

To use events to update progress or which tasks are processing, I'd extract them into their own class, which then has subscribe methods on it, and when creating a new instance of that class, assign the event to a handler in the parent class which can then update your UI or whatever you want it to do with that info.

So the content of Process() would look more like this:

Processor processor = new Processor();
    Task task = Task.Factory.StartNew(() => processor.ProcessMessage(cancellationTokenSource.CancellationToken));

    processor.StatusUpdated += ReportProcess;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM