簡體   English   中英

在Windows Service中使用TPL進行並行處理

[英]Parallel processing using TPL in windows service

我有一個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
                { }
             }
        }

我認為您的尋找將產生大量樣本。 我正在嘗試演示如何使用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
    }
}

更多例子

和想法

好的,很抱歉,我時間不多,但這是我作為替代品的一般想法/骨架。

如果我說實話,盡管我認為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;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM