รูปแบบสำหรับการมอบหมายพฤติกรรมการซิงค์ใน C #


9

ฉันพยายามออกแบบคลาสที่เปิดเผยความสามารถในการเพิ่มความกังวลในการประมวลผลแบบอะซิงโครนัส ในการเขียนโปรแกรมแบบซิงโครนัสอาจมีลักษณะเช่นนี้

   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();

ในโลกอะซิงโครนัสซึ่งความกังวลแต่ละอย่างอาจจำเป็นต้องส่งคืนงานสิ่งนี้ไม่ง่ายนัก ฉันเห็นสิ่งนี้ทำได้หลายวิธี แต่ฉันอยากรู้ว่ามีวิธีปฏิบัติที่ดีที่สุดที่ผู้คนพบ ความเป็นไปได้หนึ่งอย่างง่ายคือ

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

มี "มาตรฐาน" ที่ผู้คนนำมาใช้สำหรับเรื่องนี้หรือไม่? ดูเหมือนจะไม่มีวิธีการที่สอดคล้องกันที่ฉันสังเกตเห็นใน API ยอดนิยม


ฉันไม่แน่ใจเกี่ยวกับสิ่งที่คุณพยายามทำและทำไม
Nkosi

ฉันกำลังพยายามมอบอำนาจการดำเนินงานที่เกี่ยวข้องกับผู้สังเกตการณ์ภายนอก (คล้ายกับความหลากหลายและความปรารถนาในการจัดองค์ประกอบการสืบทอด) ส่วนใหญ่เพื่อหลีกเลี่ยงห่วงโซ่มรดกที่มีปัญหา (และเป็นไปไม่ได้จริงเพราะมันต้องมีหลายมรดก)
Jeff

ข้อกังวลเกี่ยวข้องกันในทางใดและพวกมันจะถูกดำเนินการตามลำดับหรือขนาน
Nkosi

พวกเขาดูเหมือนจะแบ่งปันการเข้าถึงProcessingArgsดังนั้นฉันจึงสับสนเกี่ยวกับเรื่องนั้น
Nkosi

1
นั่นคือประเด็นของคำถามอย่างแม่นยำ เหตุการณ์ไม่สามารถส่งคืนภารกิจได้ และแม้ว่าฉันจะใช้ผู้รับมอบสิทธิ์ที่ส่งกลับงานของทีผลที่จะได้รับหายไป
เจฟฟ์

คำตอบ:


2

ผู้รับมอบสิทธิ์ต่อไปนี้จะถูกใช้เพื่อจัดการข้อกังวลเกี่ยวกับการใช้งานแบบอะซิงโครนัส

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

จากความคิดเห็นที่ระบุไว้

ตัวอย่างหนึ่งที่เฉพาะเจาะจงคือการเพิ่มหลายขั้นตอน / งานที่จำเป็นในการ "ทำธุรกรรม" (ฟังก์ชั่น LOB)

คลาสต่อไปนี้อนุญาตให้สร้างผู้รับมอบสิทธิ์เพื่อจัดการขั้นตอนดังกล่าวในลักษณะที่คล่องแคล่วคล้ายกับมิดเดิลแวร์หลักของ. net core

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

ส่วนขยายต่อไปนี้ช่วยให้การตั้งค่าในบรรทัดง่ายขึ้นโดยใช้ตัวห่อ

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);
        });
    }
}

มันสามารถขยายเพิ่มเติมได้ตามต้องการสำหรับเสื้อคลุมเพิ่มเติม

ตัวอย่างการใช้กรณีของผู้รับมอบสิทธิ์ในการดำเนินการจะแสดงให้เห็นในการทดสอบต่อไปนี้

[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; }
    }
}

รหัสที่สวยงาม
Jeff

คุณไม่ต้องการที่จะรอต่อไปจากนั้นรอขั้นตอน? ฉันคิดว่ามันขึ้นอยู่กับว่าถ้าเพิ่มหมายความว่าคุณเพิ่มรหัสเพื่อดำเนินการก่อนรหัสอื่น ๆ ที่ได้รับการเพิ่ม วิธีที่มันเป็นเหมือน "การแทรก"
Jeff

1
ขั้นตอน @Jeff เป็นค่าเริ่มต้นที่ดำเนินการตามลำดับที่เพิ่มเข้ากับไปป์ไลน์ การตั้งค่าอินไลน์เริ่มต้นช่วยให้คุณสามารถเปลี่ยนแปลงได้ด้วยตนเองหากคุณต้องการในกรณีที่มีการดำเนินการโพสต์ที่จะทำในทางสำรองกระแส
Nkosi

คุณจะออกแบบ / แก้ไขสิ่งนี้ได้อย่างไรถ้าฉันต้องการใช้งาน Task of T แทนการตั้งค่าบริบท คุณเพิ่งจะปรับปรุงลายเซ็นและเพิ่มวิธีการแทรก (แทนที่จะเพิ่มเพียง) เพื่อให้มิดเดิลแวร์สามารถสื่อสารผลลัพธ์ของมันไปยังมิดเดิลแวร์อื่นได้หรือไม่
Jeff

1

หากคุณต้องการให้มันเป็นผู้ได้รับมอบหมายคุณสามารถ:

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;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.