C # में async व्यवहार को दर्शाने के लिए पैटर्न


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

क्या कोई "मानक" है जिसे लोगों ने इसके लिए अपनाया है? ऐसा प्रतीत नहीं होता है कि मैंने एक लोकप्रिय दृष्टिकोण देखा है जो लोकप्रिय एपीआई में देखा गया है।


मैं इस बारे में अनिश्चित हूं कि यह आप क्या और क्यों करना चाह रहे हैं।
नकोसी

मैं एक बाहरी पर्यवेक्षक (बहुरूपता के समान और वंशानुक्रम पर रचना की इच्छा) के लिए कार्यान्वयन चिंताओं को सौंपने की कोशिश कर रहा हूं। मुख्य रूप से एक समस्याग्रस्त विरासत श्रृंखला से बचने के लिए (और वास्तव में असंभव है क्योंकि इसके लिए कई विरासत की आवश्यकता होगी)।
जेफ

क्या चिंताएं किसी भी तरह से संबंधित हैं और क्या उन्हें अनुक्रम में या समानांतर रूप से संसाधित किया जाएगा?
नकोसी

उन्हें लगता है ProcessingArgsकि मैं इस बारे में उलझन में था , इसलिए हम इसे साझा कर सकते हैं ।
नकोसी

1
यह ठीक सवाल का बिंदु है। ईवेंट किसी कार्य को वापस नहीं कर सकते। और यहां तक ​​कि अगर मैं एक प्रतिनिधि का उपयोग करता हूं जो एक टास्क ऑफ टी लौटाता है, तो परिणाम खो जाएगा
जेफ

जवाबों:


2

निम्नलिखित प्रतिनिधि का उपयोग अतुल्यकालिक कार्यान्वयन चिंताओं को संभालने के लिए किया जाएगा

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

टिप्पणियों से यह संकेत दिया गया था

एक विशिष्ट उदाहरण एक "लेनदेन" (LOB कार्यक्षमता) को पूरा करने के लिए आवश्यक कई चरणों / कार्यों को जोड़ रहा है

निम्न वर्ग .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;
    }
}

निम्न एक्सटेंशन रैपर का उपयोग करके सरल इन-लाइन सेटअप की अनुमति देता है

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

सुंदर कोड।
जेफ

क्या आप अगले कदम का इंतजार नहीं करना चाहेंगे? मुझे लगता है कि यह निर्भर करता है अगर ऐड का अर्थ है कि आप किसी अन्य कोड को जोड़ने से पहले निष्पादित करने के लिए कोड जोड़ते हैं। जिस तरह से यह एक "डालने" की तरह है
जेफ

1
@Jeff चरण डिफ़ॉल्ट रूप से उस क्रम में निष्पादित होते हैं जिस क्रम में उन्हें पाइपलाइन में जोड़ा गया था। डिफ़ॉल्ट इनलाइन सेटअप आपको यह बदलने की अनुमति देता है कि यदि आप चाहें तो मैन्युअल रूप से पोस्ट अप स्ट्रीम में किए जाने वाले पोस्ट एक्शन हैं
Nkosi

यदि आप केवल संदर्भ सेट करने के बजाय परिणाम के रूप में T का उपयोग करना चाहते हैं तो आप इसे कैसे बदलेंगे / बदलेंगे। क्या आप केवल हस्ताक्षरों को अपडेट करेंगे और एक इंसर्ट विधि (केवल ऐड के बजाय) जोड़ेंगे ताकि एक मिडिलवेयर यह संवाद कर सके कि इसका परिणाम दूसरे मिडलवेयर के लिए हो सकता है?
जेफ

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;
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.