C # 'de zaman uyumsuz davranışın devri için desen


9

Eşzamansız işleme endişeleri ekleme yeteneğini gösteren bir sınıf tasarlamaya çalışıyorum. Senkron programlamada bu,

   public class ProcessingArgs : EventArgs
   {
      public int Result { get; set; }
   } 

   public class Processor 
   {
        public event EventHandler<ProcessingArgs> Processing { get; }

        public int Process()
        {
            var args = new ProcessingArgs();
            Processing?.Invoke(args);
            return args.Result;
        }
   }


   var processor = new Processor();
   processor.Processing += args => args.Result = 10;
   processor.Processing += args => args.Result+=1;
   var result = processor.Process();

her kaygının bir görevi geri vermesi gereken asenkron bir dünyada, bu o kadar basit değildir. Bunun birçok yolla yapıldığını gördüm, ancak insanların bulduğu en iyi uygulamalar olup olmadığını merak ediyorum. Basit bir olasılık

 public class Processor 
   {
        public IList<Func<ProcessingArgs, Task>> Processing { get; } =new List<Func<ProcessingArgs, Task>>();

        public async Task<int> ProcessAsync()
        {
            var args = new ProcessingArgs();
            foreach(var func in Processing) 
            {
                await func(args);
            }
            return args.Result
        }
   }

İnsanların bunun için benimsediği bir "standart" var mı? Popüler API'lerde gözlemlediğim tutarlı bir yaklaşım yok gibi görünüyor.


Ne yapmaya çalıştığın ve neden olduğu konusunda emin değilim.
Nkosi

Uygulama kaygılarını harici bir gözlemciye (polimorfizme ve kalıtım üzerine kompozisyon arzusuna benzer) devretmeye çalışıyorum. Temelde sorunlu bir miras zincirinden kaçınmak (ve birden fazla miras gerektireceği için aslında imkansız).
Jeff

Endişeler herhangi bir şekilde ilgili mi ve bunlar sırayla mı yoksa paralel mi işlenecek?
Nkosi

Onlar erişimi paylaşmak gibi görünüyor, ProcessingArgsbu yüzden bu konuda kafam karıştı.
Nkosi

1
Tam olarak sorunun konusu budur. Etkinlikler bir görevi geri alamaz. Ve T'nin Görevini döndüren bir temsilci kullansam bile, sonuç kaybolacak
Jeff

Yanıtlar:


2

Aşağıdaki temsilci, eşzamansız uygulama sorunlarını ele almak için kullanılacaktır

public delegate Task PipelineStep<TContext>(TContext context);

Yorumlardan belirtildi

Belirli bir örnek, bir "işlemi" tamamlamak için gereken birden fazla adım / görev eklemektir (LOB işlevi)

Aşağıdaki sınıf, delege oluşturmanın bu adımları .net çekirdek ara katman yazılımına benzer akıcı bir şekilde ele almasına izin verir.

public class PipelineBuilder<TContext> {
    private readonly Stack<Func<PipelineStep<TContext>, PipelineStep<TContext>>> steps =
        new Stack<Func<PipelineStep<TContext>, PipelineStep<TContext>>>();

    public PipelineBuilder<TContext> AddStep(Func<PipelineStep<TContext>, PipelineStep<TContext>> step) {
        steps.Push(step);
        return this;
    }

    public PipelineStep<TContext> Build() {
        var next = new PipelineStep<TContext>(context => Task.CompletedTask);
        while (steps.Any()) {
            var step = steps.Pop();
            next = step(next);
        }
        return next;
    }
}

Aşağıdaki uzantı, sarmalayıcıları kullanarak daha kolay satır içi kuruluma izin verir

public static class PipelineBuilderAddStepExtensions {

    public static PipelineBuilder<TContext> AddStep<TContext>
        (this PipelineBuilder<TContext> builder,
        Func<TContext, PipelineStep<TContext>, Task> middleware) {
        return builder.AddStep(next => {
            return context => {
                return middleware(context, next);
            };
        });
    }

    public static PipelineBuilder<TContext> AddStep<TContext>
        (this PipelineBuilder<TContext> builder, Func<TContext, Task> step) {
        return builder.AddStep(async (context, next) => {
            await step(context);
            await next(context);
        });
    }

    public static PipelineBuilder<TContext> AddStep<TContext>
        (this PipelineBuilder<TContext> builder, Action<TContext> step) {
        return builder.AddStep((context, next) => {
            step(context);
            return next(context);
        });
    }
}

İlave ambalajlar için gerektiğinde daha da uzatılabilir.

Aşağıdaki sınamada, delege eylem halindeki örnek bir kullanım örneği gösterilmiştir

[TestClass]
public class ProcessBuilderTests {
    [TestMethod]
    public async Task Should_Process_Steps_In_Sequence() {
        //Arrange
        var expected = 11;
        var builder = new ProcessBuilder()
            .AddStep(context => context.Result = 10)
            .AddStep(async (context, next) => {
                //do something before

                //pass context down stream
                await next(context);

                //do something after;
            })
            .AddStep(context => { context.Result += 1; return Task.CompletedTask; });

        var process = builder.Build();

        var args = new ProcessingArgs();

        //Act
        await process.Invoke(args);

        //Assert
        args.Result.Should().Be(expected);
    }

    public class ProcessBuilder : PipelineBuilder<ProcessingArgs> {

    }

    public class ProcessingArgs : EventArgs {
        public int Result { get; set; }
    }
}

Güzel kod.
Jeff

Daha sonra beklemek istemez misin? Add, eklenen herhangi bir kod önce yürütmek için kod eklemek ima ima bağlıdır. Bu daha çok bir "insert" gibi
Jeff

1
@ Jeff adımları varsayılan olarak boru hattına eklendikleri sırayla yürütülür. Varsayılan satır içi kurulum, yedekleme akışında yapılacak işlemlerin yapılması durumunda bunu manuel olarak değiştirmenizi sağlar
Nkosi

Sonuç olarak T'nin Görevini sadece bağlam ayarlamak yerine kullanmak istersem bunu nasıl tasarlarsınız / değiştirirsiniz? İmzaları günceller ve bir ara katman yazılımının sonucunu başka bir ara katman yazılımına iletebilmesi için bir Ekle yöntemi (yalnızca Ekle yerine) ekler misiniz?
Jeff

1

Temsilci olarak tutmak istiyorsanız şunları yapabilirsiniz:

public class Processor
{
    public event Func<ProcessingArgs, Task> Processing;

    public async Task<int?> ProcessAsync()
    {
        if (Processing?.GetInvocationList() is Delegate[] processors)
        {
            var args = new ProcessingArgs();
            foreach (Func<ProcessingArgs, Task> processor in processors)
            {
                await processor(args);
            }
            return args.Result;
        }
        else return null;
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.