简体   繁体   English

在Windows Service中使用TPL进行并行处理

[英]Parallel processing using TPL in windows service

I have a windows service which is consuming a messaging system to fetch messages. 我有一个Windows服务,正在使用消息传递系统来获取消息。 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. 我还在Timer类的帮助下创建了一个回调机制,该机制可以帮助我在固定时间获取和处理消息后检查消息。 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> . 我正在尝试演示如何使用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 在示例中, ActionBlock将并行处理和处理从消息传递系统收到的所有消息

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. 如果我说实话,尽管我认为ActionBlock<T>是更好的选择,因为您要做的事太多了,唯一的限制是您无法动态扩展将要执行的工作量,尽管我认为限制可能会很高。 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. 如果您以这种方式进行操作,则可以拥有更多的控制权,或者只是动态地执行一定数量的任务,但是您必须手动执行许多操作,例如,如果要限制在某个时间点运行的任务数量时间,您必须实现一个排队系统(ActionBlock可以为您处理),然后对其进行维护。 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. 我认为最主要的事情是使Callback运行的方法触发执行该任务的线程,而不是在单独的线程中进行订阅。

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: 我像您一样使用Task.Factory.StartNew ,但是将返回的Task对象存储在一个对象( TaskInfo )中,该对象也具有CancellationTokenSource,它的ID(在外部分配)作为属性,然后将其添加到TaskInfo的集合中类的所有属性都属于:

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. 您必须对其进行更新,并在诸如HeartbeatController类的地方填充空白,以及由于事件超出问题范围而被调用的少数事件,但是想法是相同的。

    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. 要使用事件更新进度或正在处理的任务,我将它们提取到自己的类中,然后在其上具有订阅方法,并在创建该类的新实例时,将该事件分配给父类中的处理程序然后可以更新您的UI或您要使用该信息执行的任何操作。

So the content of Process() would look more like this: 因此, Process()的内容将更像这样:

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

    processor.StatusUpdated += ReportProcess;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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