简体   繁体   English

是否可以在TPL数据流中每个链接块一次仅处理一个任务?

[英]Is there a way to only process one task at a time per linked block in TPL Dataflow?

I have two classes, serviceclient, and a service. 我有两个类,serviceclient和一个服务。 The serviceclient will generate messages that will be processed by the service. serviceclient将生成将由服务处理的消息。 ServiceClient messages should be processed in a strictly FIFO order, with the next message only available when the previous one was finished processing. ServiceClient消息应严格按照FIFO顺序进行处理,下一条消息仅在上一条消息完成处理后才可用。

To address this issue I put an action block in each serviceclient that calls the service directly to process the client message which works fine I think but requires the additional dependency injection. 为了解决此问题,我在每个serviceclient中放置了一个动作块,该块直接调用服务以处理客户端消息,我认为这很好,但需要附加的依赖项注入。 I was wondering if there is a way to set it up so that the service can link directly to the serviceclient messageblock so that it can concurrently process messages from multiple serviceclient message blocks, but only one message at a time from any particular serviceclient? 我想知道是否可以设置它,以便该服务可以直接链接到serviceclient消息块,以便它可以同时处理来自多个serviceclient消息块的消息,但一次只能处理来自某个特定serviceclient的消息?

some code with the desired functionality:: 一些具有所需功能的代码:

    static void Main(string[] args)
    {
        var service = new Service();

        //I would like messages from these clients to be processed concurrently by service, but only one at a time per client. 
        //So if Client A has two messages in queue(a1, a2) and B has 3(b1,b2,b3), it will immediately take a1&b1. If a1 finishes, it will then take a2. if b1 finishes it will take b2, same with b2 and b3. It would never process a1 concurrently with a2, or b1 concurrently with b2 or b3. 
        service.AddClient(new ServiceClient());
        service.AddClient(new ServiceClient());
    }

    interface IServiceMessage
    {
        string Message { get; }
    }

    class ServiceClient
    {
        public BufferBlock<IServiceMessage> clientServiceMsgs = new BufferBlock<IServiceMessage>();

        public ServiceClient() {
            //run task to populate bufferblock 
        }
    }

    class Service
    {
        ActionBlock<IServiceMessage> processServiceMsgsBlock;

        public Service() {
            processServiceMsgsBlock = new ActionBlock<IServiceMessage>(ProcessServiceMessage);
        }  

        public async Task ProcessServiceMessage(IServiceMessage msg) {
            //process stuff
            return;
        }

        public void AddClient(ServiceClient client)
        {
            client.clientServiceMsgs.LinkTo(processServiceMsgsBlock);
        }
    }

Do you really need TPL DataFlow here? 您真的需要这里的TPL DataFlow吗? I will give you two options without, then finally one with TPL Dataflow like you ask. 我将给您两个选项,不带任何选项,最后给您一个使用TPL Dataflow的选项。

Each option shares the below. 每个选项共享以下内容。

public class ServiceClient
{
    public async Task<IServiceMessage> GetAsync(string filter)
    {
        await Task.Yield();
        return new ServiceMessage() { Message = Guid.NewGuid().ToString() };
    }
}

public class Service
{
    public async Task ProcessAsync(IServiceMessage message)
    {
        // do something with it
        await Task.Delay(10);
    }
}

public interface IServiceMessage
{
    string Message { get; }
}

public class ServiceMessage : IServiceMessage
{
    public string Message { get; set; }
}

Option 1 - A service kicks off a task to read and write data. 选项1-服务启动一项读取和写入数据的任务。 Simply couple the Service and ServiceClient together in a third service. 只需将Service和ServiceClient耦合到第三项服务中即可。

class Program
{
    static void Main(string[] args)
    {
        var cts = new CancellationTokenSource();
        var service = new Service();
        var serviceClient = new ServiceClient();
        var processor = new ProducerConsumerService(serviceClient, service);
        processor.Process("A", cts.Token);
        processor.Process("B", cts.Token);
        processor.Process("C", cts.Token);
        processor.Process("D", cts.Token);

        Console.WriteLine("Press any key to shutdown");
        Console.Read();

        cts.Cancel();
        processor.WaitForCompletion();
    }
}

public class ProducerConsumerService
{
    private List<Task> _processTasks;
    private ServiceClient _serviceClient;
    private Service _service;

    public ProducerConsumerService(ServiceClient serviceClient, Service service)
    {
        _serviceClient = serviceClient;
        _service = service;

        _processTasks = new List<Task>();
    }

    public void Process(string filter, CancellationToken token)
    {
        _processTasks.Add(Task.Run(() =>
        {
            while (!token.IsCancellationRequested)
            {
                var message = _serviceClient.Get(filter);
                _service.Process(message);
            }
        }));
    }

    public void WaitForCompletion()
    {
        Task.WaitAll(_processTasks.ToArray(), TimeSpan.FromSeconds(10));
    }
}

Option 2 - Same as Option 1 but with two tasks and a BlockingCollection which provides a bounded buffer between the producer (ServiceClient) and the consumer (Service). 选项2-与选项1相同,但是有两个任务和一个BlockingCollection,它在生产者(ServiceClient)和使用者(Service)之间提供了有限的缓冲区。

public class ProducerBufferConsumerService
{
    private List<Task> _producerTasks;
    private List<Task> _consumerTasks;
    private ServiceClient _serviceClient;
    private Service _service;

    public ProducerBufferConsumerService(ServiceClient serviceClient, Service service)
    {
        _serviceClient = serviceClient;
        _service = service;

        _producerTasks = new List<Task>();
        _consumerTasks = new List<Task>();
    }

    public void Process(CancellationToken token)
    {
        var buffer = new BlockingCollection<IServiceMessage>(1000);
        _producerTasks.Add(Task.Run(async () =>
        {
            while (!token.IsCancellationRequested)
            {
                var message = await _serviceClient.GetAsync();
                buffer.Add(message, token);
            }

            buffer.CompleteAdding();
        }));

        _consumerTasks.Add(Task.Run(async () =>
        {
            while (!token.IsCancellationRequested && !buffer.IsAddingCompleted)
            {
                var message = buffer.Take(token);
                await _service.ProcessAsync(message);
            }
        }));
    }

    public void WaitForCompletion()
    {
        Task.WaitAll(_producerTasks.ToArray(), 10000);
        Task.WaitAll(_consumerTasks.ToArray(), 10000);
    }
}

Option 3 - Parallelize processing while maintaining order of a given ServiceClient 选项3-并行处理,同时维持给定ServiceClient的顺序

Not exactly what you requested, but close. 并非完全符合您的要求,但是关闭了。 You can parallelize your incoming ServiceClient data streams while maintaining correct ordering. 您可以并行化传入的ServiceClient数据流,同时保持正确的顺序。 In this example all ServiceClients post to a single block, which then feeds into 9 partitioned actionblocks.. 在此示例中,所有ServiceClient均发布到单个块,然后将其馈入9个分区的动作块中。

Provide each message of a given service client a Guid Id. 为给定服务客户的每条消息提供一个Guid ID。 Then use GetHashCode() to turn that into a number. 然后使用GetHashCode()将其转换为数字。 Reduce that number to a range of 1-9. 将该数字减少到1-9的范围。 Create 9 ActionBlocks and use the lambda in the LinkTo method to limit the link to a single number in that range. 创建9个ActionBlock,并在LinkTo方法中使用lambda将链接限制为该范围内的单个数字。 That way we create 9 partitions which each process a sub-range of the data. 这样,我们创建了9个分区,每个分区处理一个数据子范围。 You can safely process in a parallelised way with independent actions while maintaining FIFO of a given id. 您可以在保持给定id的FIFO的同时,以独立的操作安全地并行处理。

class Program
{
    static void Main(string[] args)
    {
        var cts = new CancellationTokenSource();
        var filters = new List<string>() { "A", "B", "C", "D" };
        var service = new Service();
        var serviceClient = new ServiceClient();
        var partitioningService = new PartitioningService(serviceClient, service);
        var processingTask = Task.Run(() => partitioningService.Process(filters, cts.Token));


        Console.WriteLine("Press any key to shutdown");
        Console.ReadKey();

        cts.Cancel();
        processingTask.Wait(10000);
    }
}

public interface IServiceMessage
{
    string Message { get; }
    Guid Id { get; set; }
}

public class ServiceMessage : IServiceMessage
{
    public string Message { get; set; }
    public Guid Id { get; set;  }
}

public class RoutedMessage
{
    public IServiceMessage Message { get; set; }
    public int PartitionId { get; set; }
}

public class PartitioningService
{
    private ServiceClient _serviceClient;
    private Service _service;

    public PartitioningService(ServiceClient serviceClient, Service service)
    {
        _serviceClient = serviceClient;
        _service = service;
    }

    public void Process(List<string> filters, CancellationToken token)
    {
        var linkOptions = new DataflowLinkOptions { PropagateCompletion = true };
        Func<IServiceMessage, RoutedMessage> partitioner = x => new RoutedMessage
            { 
                Message = x,
                PartitionId = x.Id.GetHashCode() / 1000000000
        };

        var partitionerBlock = new TransformBlock<IServiceMessage, RoutedMessage>(partitioner);
        var actionBlock1 = new ActionBlock<RoutedMessage>(async (RoutedMessage msg) => await _service.ProcessAsync(msg));
        var actionBlock2 = new ActionBlock<RoutedMessage>(async (RoutedMessage msg) => await _service.ProcessAsync(msg));
        var actionBlock3 = new ActionBlock<RoutedMessage>(async (RoutedMessage msg) => await _service.ProcessAsync(msg));
        var actionBlock4 = new ActionBlock<RoutedMessage>(async (RoutedMessage msg) => await _service.ProcessAsync(msg));
        var actionBlock5 = new ActionBlock<RoutedMessage>(async (RoutedMessage msg) => await _service.ProcessAsync(msg));
        var actionBlock6 = new ActionBlock<RoutedMessage>(async (RoutedMessage msg) => await _service.ProcessAsync(msg));
        var actionBlock7 = new ActionBlock<RoutedMessage>(async (RoutedMessage msg) => await _service.ProcessAsync(msg));
        var actionBlock8 = new ActionBlock<RoutedMessage>(async (RoutedMessage msg) => await _service.ProcessAsync(msg));
        var actionBlock9 = new ActionBlock<RoutedMessage>(async (RoutedMessage msg) => await _service.ProcessAsync(msg));

        partitionerBlock.LinkTo(actionBlock1, linkOptions, msg => msg.PartitionId == -4);
        partitionerBlock.LinkTo(actionBlock1, linkOptions, msg => msg.PartitionId == -3);
        partitionerBlock.LinkTo(actionBlock1, linkOptions, msg => msg.PartitionId == -2);
        partitionerBlock.LinkTo(actionBlock1, linkOptions, msg => msg.PartitionId == -1);
        partitionerBlock.LinkTo(actionBlock1, linkOptions, msg => msg.PartitionId == 0);
        partitionerBlock.LinkTo(actionBlock1, linkOptions, msg => msg.PartitionId == 1);
        partitionerBlock.LinkTo(actionBlock1, linkOptions, msg => msg.PartitionId == 2);
        partitionerBlock.LinkTo(actionBlock1, linkOptions, msg => msg.PartitionId == 3);
        partitionerBlock.LinkTo(actionBlock1, linkOptions, msg => msg.PartitionId == 4);

        var tasks = new List<Task>();
        foreach (var filter in filters)
        {
            tasks.Add(Task.Run(async () =>
                {
                    Guid filterId = Guid.NewGuid();

                    while (!token.IsCancellationRequested)
                    {
                        var message = await _serviceClient.GetAsync(filter);
                        message.Id = filterId;
                        await partitionerBlock.SendAsync(message);
                    }
                }));
        }

        while (!token.IsCancellationRequested)
            Thread.Sleep(100);

        partitionerBlock.Complete();
        actionBlock1.Completion.Wait();
        actionBlock2.Completion.Wait();
        actionBlock3.Completion.Wait();
        actionBlock4.Completion.Wait();
        actionBlock5.Completion.Wait();
        actionBlock6.Completion.Wait();
        actionBlock7.Completion.Wait();
        actionBlock8.Completion.Wait();
        actionBlock9.Completion.Wait();

        Task.WaitAll(tasks.ToArray(), 10000);
    }
}

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

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