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

时间:2019-02-25 14:43:51

标签: c# parallel-processing task task-parallel-library

我有一个Windows服务,该服务正在使用消息传递系统来获取消息。我还在Timer类的帮助下创建了一个回调机制,该机制可以帮助我在固定时间获取和处理消息后检查消息。以前,服务正在逐个处理消息。但是我希望消息到达后,处理机制可以并行执行。因此,如果第一条消息到达,则应继续处理一个任务,即使在使用回调方法配置的间隔时间之后,第一条消息的处理仍未完成(回调现在正在工作),也应选择下一条消息并对其进行处理另一个任务。

下面是我的代码:

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
 {

 }
}

但是使用“任务工厂”时,我无法并行控制任务的数量,因此我想根据任务的可用性配置将在其上运行消息的任务的数量?


更新: 更新了我上面的代码以添加多个任务

下面是代码:

         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
                { }
             }
        }

2 个答案:

答案 0 :(得分:0)

我认为您的寻找将产生大量样本。我正在尝试演示如何使用ActionBlock<T>来完成此操作。仍然有许多未知数,因此我将样本作为可以构建的骨架。在示例中,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

答案 1 :(得分:0)

好的,很抱歉,我的时间不多,但这是我作为替代品的一般想法/骨架。

如果我说实话,尽管我认为ActionBlock<T>是更好的选择,但您要做的事太多了,唯一的限制是您无法动态扩展将要完成的工作量一次,尽管我认为限制可能会很高。如果您以这种方式进行操作,则可以拥有更多的控制权,或者只是动态地执行一定数量的任务,但是您必须手动执行许多操作,例如,如果要限制在某个时间点运行的任务数量时间,您必须实现一个排队系统(ActionBlock可以为您处理),然后对其进行维护。我想这取决于您收到的消息数量以及您的进程处理它们的速度。

您必须进行检查,并考虑如何将其应用于您的直接用例,因为我认为在并发包的想法方面,我这一方面有些粗略地实现了。

因此,我在此处提出的想法是,您可以启动任意数量的任务,或者使用集合将这些任务添加到正在运行的任务中,或者分别取消任务。

我想的主要事情是使Callback运行的方法触发执行工作的线程,而不是在单独的线程中进行订阅。

我和您一样使用Task.Factory.StartNew,但是将返回的Task对象存储在对象(TaskInfo)中,该对象也具有CancellationTokenSource,它的ID(在外部分配)作为属性,然后将其添加到TaskInfo的集合,它是类的一个属性,它全部是以下内容的一部分:

已更新-为避免混淆,我刚刚更新了以前的代码。

您必须对其进行更新,并在诸如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
}
}

希望这可以让您了解如何解决您的问题,而我并没有忘记要点:)。


更新

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

所以Process()的内容看起来像这样:

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

    processor.StatusUpdated += ReportProcess;