Mẫu để ủy thác hành vi không đồng bộ trong C #


9

Tôi đang cố gắng thiết kế một lớp thể hiện khả năng thêm các mối quan tâm xử lý không đồng bộ. Trong lập trình đồng bộ, điều này có thể trông giống như

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

trong một thế giới không đồng bộ, nơi mỗi mối quan tâm có thể cần phải trả lại một nhiệm vụ, điều này không đơn giản như vậy. Tôi đã thấy điều này được thực hiện rất nhiều cách, nhưng tôi tò mò liệu có bất kỳ thực hành tốt nhất mà mọi người đã tìm thấy. Một khả năng đơn giản là

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

Có một số "tiêu chuẩn" mà mọi người đã áp dụng cho điều này? Dường như không có một cách tiếp cận nhất quán mà tôi đã quan sát trên các API phổ biến.


Tôi không chắc chắn về những gì bạn đang cố gắng làm và tại sao.
Nkosi

Tôi đang cố gắng ủy thác các mối quan tâm thực hiện cho một người quan sát bên ngoài (tương tự như đa hình và mong muốn sáng tác hơn sự kế thừa). Chủ yếu là để tránh một chuỗi thừa kế có vấn đề (và thực tế là không thể vì nó sẽ đòi hỏi nhiều kế thừa).
Jeff

Là những mối quan tâm liên quan theo bất kỳ cách nào và chúng sẽ được xử lý theo trình tự hoặc song song?
Nkosi

Họ dường như chia sẻ quyền truy cập vào ProcessingArgsvì vậy tôi đã nhầm lẫn về điều đó.
Nkosi

1
Đó chính xác là điểm của câu hỏi. Sự kiện không thể trả lại một nhiệm vụ. Và ngay cả khi tôi sử dụng một đại biểu trả về Nhiệm vụ của T, kết quả sẽ bị mất
Jeff

Câu trả lời:


2

Đại biểu sau sẽ được sử dụng để xử lý các mối quan tâm triển khai không đồng bộ

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

Từ các ý kiến, nó đã được chỉ định

Một ví dụ cụ thể là thêm nhiều bước / nhiệm vụ cần thiết để hoàn thành "giao dịch" (chức năng LOB)

Lớp sau đây cho phép xây dựng một đại biểu để xử lý các bước như vậy một cách trôi chảy tương tự như phần mềm trung gian lõi .net

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

Tiện ích mở rộng sau đây cho phép thiết lập nội tuyến đơn giản hơn bằng cách sử dụng trình bao bọc

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

Nó có thể được mở rộng hơn nữa khi cần thiết cho các trình bao bọc bổ sung.

Một ví dụ về trường hợp sử dụng của đại biểu đang hoạt động được thể hiện trong thử nghiệm sau

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

Mã đẹp.
Jeff

Bạn có muốn chờ đợi tiếp theo sau đó chờ bước không? Tôi đoán nó phụ thuộc nếu Add ngụ ý rằng bạn thêm mã để thực thi trước bất kỳ mã nào khác đã được thêm vào. Cách nó giống như một trò chèn chèn trên mạng
Jeff

1
Các bước @Jeff theo mặc định được thực hiện theo thứ tự chúng đã được thêm vào đường ống. Thiết lập nội tuyến mặc định cho phép bạn thay đổi điều đó theo cách thủ công nếu bạn muốn trong trường hợp có các hành động đăng bài được thực hiện trên đường sao lưu luồng
Nkosi

Bạn sẽ thiết kế / thay đổi điều này như thế nào nếu tôi muốn sử dụng Nhiệm vụ của T thay vì chỉ thiết lập bối cảnh.Result? Bạn có thể cập nhật chữ ký và thêm phương thức Chèn (thay vì chỉ Thêm) để phần mềm trung gian có thể giao tiếp kết quả của nó với phần mềm trung gian khác không?
Jeff

1

Nếu bạn muốn giữ nó làm đại biểu, bạn có thể:

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;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.