2012-10-12 16 views
7

नया एसिंक/प्रतीक्षा मॉडल का उपयोग करना Task उत्पन्न करने के लिए यह काफी सरल है जो किसी ईवेंट को आग लगने पर पूरा हो जाता है;सामान्य उद्देश्य FromEvent विधि

public class MyClass 
{ 
    public event Action OnCompletion; 
} 

public static Task FromEvent(MyClass obj) 
{ 
    TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(); 

    obj.OnCompletion +=() => 
     { 
      tcs.SetResult(null); 
     }; 

    return tcs.Task; 
} 

यह तो अनुमति देता है::

await FromEvent(new MyClass()); 

समस्या यह है कि आप हर कक्षा में हर घटना है कि आप await चाहते हैं के लिए एक नया FromEvent विधि बनाने की जरूरत है आप सिर्फ इस पैटर्न का पालन करने की जरूरत है पर। यह वास्तव में वास्तव में बहुत तेज़ हो सकता है, और यह वैसे भी बॉयलरप्लेट कोड है।

आदर्श रूप में मैं इस तरह कुछ करने के लिए सक्षम होने के लिए करना चाहते हैं:

await FromEvent(new MyClass().OnCompletion); 

तो मैं कर सकता है एक ही FromEvent विधि किसी भी घटना पर किसी भी घटना के लिए फिर से इस्तेमाल करते हैं। मैंने इस तरह की एक विधि बनाने की कोशिश करने में कुछ समय बिताया है, और कई स्नैग हैं। के लिए यह ऊपर कोड निम्न त्रुटि उत्पन्न करेगा: जहां तक ​​मेरा बता सकते हैं

The event 'Namespace.MyClass.OnCompletion' can only appear on the left hand side of += or -=

, वहाँ कभी कोड के माध्यम से इस तरह घटना पास करने का तरीका नहीं होगा।

तो, अगली सबसे अच्छी बात एक स्ट्रिंग के रूप घटना नाम पारित करने के लिए कोशिश कर लग रहा था:

await FromEvent(new MyClass(), "OnCompletion"); 

यह रूप में आदर्श नहीं है; आपको इंटेलिजेंस नहीं मिलता है और यदि रन उस प्रकार के लिए मौजूद नहीं है, तो यह रनटाइम त्रुटि प्राप्त करेगा, लेकिन यह अभी भी FromEvent विधियों के टन से अधिक उपयोगी हो सकता है।

तो EventInfo ऑब्जेक्ट प्राप्त करने के लिए प्रतिबिंब और GetEvent(eventName) का उपयोग करना काफी आसान है। अगली समस्या यह है कि उस घटना के प्रतिनिधि को रनटाइम पर ज्ञात नहीं है (और अलग-अलग करने में सक्षम होना चाहिए)। इससे इवेंट हैंडलर को कड़ी मेहनत मिलती है, क्योंकि हमें रनटाइम पर गतिशील रूप से एक विधि बनाने की आवश्यकता होती है, किसी दिए गए हस्ताक्षर से मिलान करना (लेकिन सभी पैरामीटर को अनदेखा करना) जो TaskCompletionSource तक पहुंचता है जो हमारे पास पहले से है और उसका परिणाम सेट करता है।

सौभाग्य से मुझे this link मिला जिसमें [लगभग] वास्तव में Reflection.Emit के माध्यम से निर्देशों के बारे में निर्देश शामिल हैं। अब समस्या यह है कि हमें आईएल उत्सर्जित करने की आवश्यकता है, और मुझे नहीं पता कि मेरे पास tcs उदाहरण का उपयोग कैसे किया जाए।

public static Task FromEvent<T>(this T obj, string eventName) 
{ 
    var tcs = new TaskCompletionSource<object>(); 
    var eventInfo = obj.GetType().GetEvent(eventName); 

    Type eventDelegate = eventInfo.EventHandlerType; 

    Type[] parameterTypes = GetDelegateParameterTypes(eventDelegate); 
    DynamicMethod handler = new DynamicMethod("unnamed", null, parameterTypes); 

    ILGenerator ilgen = handler.GetILGenerator(); 

    //TODO ilgen.Emit calls go here 

    Delegate dEmitted = handler.CreateDelegate(eventDelegate); 

    eventInfo.AddEventHandler(obj, dEmitted); 

    return tcs.Task; 
} 

क्या आईएल मैं संभवतः फेंकना सकता है कि मुझे TaskCompletionSource का परिणाम सेट करने की अनुमति होगी:

नीचे प्रगति है कि मैं इस परिष्करण की ओर कर दिया है है? या, वैकल्पिक रूप से, क्या कोई तरीका बनाने के लिए कोई और तरीका है जो किसी मनमानी घटना से किसी मनमानी घटना के लिए कार्य देता है?

+2

ध्यान दें कि बीसीएल में 'TaskFactory.FromAsync' है जो आसानी से एपीएम से टीएपी में अनुवाद करने के लिए है। ईएपी से टीएपी में अनुवाद करने के लिए एक आसान * और * सामान्य तरीका नहीं है, इसलिए मुझे लगता है कि एमएस में इस तरह का समाधान शामिल नहीं था। मुझे आरएक्स (या टीपीएल डेटाफ्लो) किसी भी तरह से "ईवेंट" सेमेन्टिक्स के करीब मिलान करने के लिए मिलता है - और आरएक्स * करता है * एक 'FromEvent' विधि है। –

+1

मैं भी एक सामान्य 'FromEvent <>' बनाना चाहता था, और [यह] (http://stackoverflow.com/a/22798789/1768303) करीब है क्योंकि मैं प्रतिबिंब का उपयोग किए बिना उस पर पहुंच सकता हूं। – Noseratio

उत्तर

21

ये रहा:

internal class TaskCompletionSourceHolder 
{ 
    private readonly TaskCompletionSource<object[]> m_tcs; 

    internal object Target { get; set; } 
    internal EventInfo EventInfo { get; set; } 
    internal Delegate Delegate { get; set; } 

    internal TaskCompletionSourceHolder(TaskCompletionSource<object[]> tsc) 
    { 
     m_tcs = tsc; 
    } 

    private void SetResult(params object[] args) 
    { 
     // this method will be called from emitted IL 
     // so we can set result here, unsubscribe from the event 
     // or do whatever we want. 

     // object[] args will contain arguments 
     // passed to the event handler 
     m_tcs.SetResult(args); 
     EventInfo.RemoveEventHandler(Target, Delegate); 
    } 
} 

public static class ExtensionMethods 
{ 
    private static Dictionary<Type, DynamicMethod> s_emittedHandlers = 
     new Dictionary<Type, DynamicMethod>(); 

    private static void GetDelegateParameterAndReturnTypes(Type delegateType, 
     out List<Type> parameterTypes, out Type returnType) 
    { 
     if (delegateType.BaseType != typeof(MulticastDelegate)) 
      throw new ArgumentException("delegateType is not a delegate"); 

     MethodInfo invoke = delegateType.GetMethod("Invoke"); 
     if (invoke == null) 
      throw new ArgumentException("delegateType is not a delegate."); 

     ParameterInfo[] parameters = invoke.GetParameters(); 
     parameterTypes = new List<Type>(parameters.Length); 
     for (int i = 0; i < parameters.Length; i++) 
      parameterTypes.Add(parameters[i].ParameterType); 

     returnType = invoke.ReturnType; 
    } 

    public static Task<object[]> FromEvent<T>(this T obj, string eventName) 
    { 
     var tcs = new TaskCompletionSource<object[]>(); 
     var tcsh = new TaskCompletionSourceHolder(tcs); 

     EventInfo eventInfo = obj.GetType().GetEvent(eventName); 
     Type eventDelegateType = eventInfo.EventHandlerType; 

     DynamicMethod handler; 
     if (!s_emittedHandlers.TryGetValue(eventDelegateType, out handler)) 
     { 
      Type returnType; 
      List<Type> parameterTypes; 
      GetDelegateParameterAndReturnTypes(eventDelegateType, 
       out parameterTypes, out returnType); 

      if (returnType != typeof(void)) 
       throw new NotSupportedException(); 

      Type tcshType = tcsh.GetType(); 
      MethodInfo setResultMethodInfo = tcshType.GetMethod(
       "SetResult", BindingFlags.NonPublic | BindingFlags.Instance); 

      // I'm going to create an instance-like method 
      // so, first argument must an instance itself 
      // i.e. TaskCompletionSourceHolder *this* 
      parameterTypes.Insert(0, tcshType); 
      Type[] parameterTypesAr = parameterTypes.ToArray(); 

      handler = new DynamicMethod("unnamed", 
       returnType, parameterTypesAr, tcshType); 

      ILGenerator ilgen = handler.GetILGenerator(); 

      // declare local variable of type object[] 
      LocalBuilder arr = ilgen.DeclareLocal(typeof(object[])); 
      // push array's size onto the stack 
      ilgen.Emit(OpCodes.Ldc_I4, parameterTypesAr.Length - 1); 
      // create an object array of the given size 
      ilgen.Emit(OpCodes.Newarr, typeof(object)); 
      // and store it in the local variable 
      ilgen.Emit(OpCodes.Stloc, arr); 

      // iterate thru all arguments except the zero one (i.e. *this*) 
      // and store them to the array 
      for (int i = 1; i < parameterTypesAr.Length; i++) 
      { 
       // push the array onto the stack 
       ilgen.Emit(OpCodes.Ldloc, arr); 
       // push the argument's index onto the stack 
       ilgen.Emit(OpCodes.Ldc_I4, i - 1); 
       // push the argument onto the stack 
       ilgen.Emit(OpCodes.Ldarg, i); 

       // check if it is of a value type 
       // and perform boxing if necessary 
       if (parameterTypesAr[i].IsValueType) 
        ilgen.Emit(OpCodes.Box, parameterTypesAr[i]); 

       // store the value to the argument's array 
       ilgen.Emit(OpCodes.Stelem, typeof(object)); 
      } 

      // load zero-argument (i.e. *this*) onto the stack 
      ilgen.Emit(OpCodes.Ldarg_0); 
      // load the array onto the stack 
      ilgen.Emit(OpCodes.Ldloc, arr); 
      // call this.SetResult(arr); 
      ilgen.Emit(OpCodes.Call, setResultMethodInfo); 
      // and return 
      ilgen.Emit(OpCodes.Ret); 

      s_emittedHandlers.Add(eventDelegateType, handler); 
     } 

     Delegate dEmitted = handler.CreateDelegate(eventDelegateType, tcsh); 
     tcsh.Target = obj; 
     tcsh.EventInfo = eventInfo; 
     tcsh.Delegate = dEmitted; 

     eventInfo.AddEventHandler(obj, dEmitted); 
     return tcs.Task; 
    } 
} 

इस कोड को लगभग सभी घटनाओं वापसी शून्य (पैरामीटर सूची की परवाह किए बिना) के लिए काम करेंगे।

यदि आवश्यक हो तो इसे किसी भी वापसी मूल्य का समर्थन करने के लिए बेहतर किया जा सकता है।

आप डेक्स के और नीचे मेरा विधियों के बीच अंतर देख सकते हैं:

static async void Run() { 
    object[] result = await new MyClass().FromEvent("Fired"); 
    Console.WriteLine(string.Join(", ", result.Select(arg => 
     arg.ToString()).ToArray())); // 123, abcd 
} 

public class MyClass { 
    public delegate void TwoThings(int x, string y); 

    public MyClass() { 
     new Thread(() => { 
       Thread.Sleep(1000); 
       Fired(123, "abcd"); 
      }).Start(); 
    } 

    public event TwoThings Fired; 
} 

संक्षेप में, मेरी कोड वास्तव में प्रतिनिधि प्रकार के किसी भी प्रकार का समर्थन करता है। आपको इसे स्पष्ट रूप से TaskFromEvent<int, string> की तरह निर्दिष्ट नहीं करना चाहिए (और इसकी आवश्यकता नहीं है)।

+0

मैंने अभी अपना अपडेट देखकर थोड़ा सा खेल रहा है। मैं वास्तव में हूं इसे पसंद करना। इवेंट हैंडलर सदस्यता रहित है, जो एक अच्छा स्पर्श है। विभिन्न इवेंट हैंडलर कैश किए जाते हैं, इसलिए आईएल समान प्रकार के लिए बार-बार उत्पन्न नहीं होता है, और अन्य समाधानों के विपरीत, तर्कों के प्रकार निर्दिष्ट करने की आवश्यकता नहीं होती है इवेंट हैंडलर को। – Servy

+0

मैं विंडोज फोन पर कोड काम नहीं कर सका, यह नहीं पता कि यह एक सुरक्षा समस्या है या नहीं। लेकिन काम नहीं किया .. अपवाद: {"विधि तक पहुंचने का प्रयास विफल: System.Reflection.Emit.DynamicMethod ..क्टर (सिस्टम। स्टिंग, सिस्ट em.Type, System.Type [], System.Type) "} –

+1

@ जे। लिनन दुर्भाग्यवश, मैं इसे विंडोज फोन पर परीक्षण करने में सक्षम नहीं हूं। तो यदि आप इस [** अपडेट किए गए संस्करण **] (http://pastebin.com/4za6pdzA) का उपयोग करने का प्रयास कर सकते हैं तो मैं वास्तव में आभारी रहूंगा और अगर यह मदद करता है तो मुझे बताएं। अग्रिम में धन्यवाद। –

2

आप प्रतिनिधि प्रकार प्रति एक विधि के लिए तैयार हैं, तो आप की तरह कुछ कर सकते हैं:

Task FromEvent(Action<Action> add) 
{ 
    var tcs = new TaskCompletionSource<bool>(); 

    add(() => tcs.SetResult(true)); 

    return tcs.Task; 
} 

आप इसे पसंद का प्रयोग करेंगे:

await FromEvent(x => new MyClass().OnCompletion += x); 

ध्यान रखें कि इस तरह से आप कभी नहीं घटना से सदस्यता समाप्त करें, जो आपके लिए कोई समस्या हो सकती है या नहीं भी हो सकती है।

आप सामान्य प्रतिनिधियों का उपयोग कर रहे हैं तो प्रत्येक सामान्य प्रकार के अनुसार एक विधि के लिए पर्याप्त है, तो आप प्रत्येक ठोस प्रकार के लिए एक की जरूरत नहीं है: प्रकार निष्कर्ष है कि साथ काम नहीं करता

Task<T> FromEvent<T>(Action<Action<T>> add) 
{ 
    var tcs = new TaskCompletionSource<T>(); 

    add(x => tcs.SetResult(x)); 

    return tcs.Task; 
} 

हालांकि, आप स्पष्ट रूप से प्रकार पैरामीटर निर्दिष्ट करना (OnCompletion के प्रकार यह सोचते हैं यहाँ Action<string> है):

string s = await FromEvent<string>(x => c.OnCompletion += x); 
+0

मुख्य समस्या यह है कि यूआई ढांचे में से कई प्रत्येक घटना के लिए अपने स्वयं के प्रतिनिधि प्रकार बनाते हैं ('एक्शन '/'EventHandler ') का उपयोग करने के बजाय, और ऐसा कुछ है जहां ऐसा कुछ सबसे उपयोगी होगा, इसलिए एक बनाना प्रत्येक प्रतिनिधि प्रकार के लिए 'FromEvent' विधि * बेहतर * होगी, लेकिन फिर भी सही नहीं है। उस ने कहा, आप केवल पहली विधि बना सकते हैं और उपयोग कर सकते हैं: 'प्रतीक्षा करें (x => नया MyClass()। ऑनकंपलेशन + = (ए, बी) => एक्स()); किसी भी घटना पर। यह एक आधे रास्ते के समाधान है। – Servy

+0

@ सर्वी हाँ, हालांकि मैं इसे इस तरह से करने के बारे में भी, लेकिन मैंने इसका जिक्र नहीं किया क्योंकि मुझे लगता है कि यह बदसूरत है (यानी बहुत अधिक बॉयलरप्लेट)। – svick

+0

यह समाधान एक बहुत बदसूरत और उपयोग करने में कठोर है = (जब मैंने कोड लिखा था: wtf !? –

5

यह आपको दे देंगे क्या आप किसी भी Ilgen, और जिस तरह से सरल नहीं करना पड़ता की जरूरत है। यह किसी भी तरह के घटना प्रतिनिधियों के साथ काम करता है; आपको बस अपने ईवेंट प्रतिनिधि में प्रत्येक पैरामीटर के लिए एक अलग हैंडलर बनाना होगा। नीचे हैंडलर हैं जिनकी आपको आवश्यकता होगी 0..2, जो आपके उपयोग मामलों का विशाल बहुमत होना चाहिए। 3 और ऊपर तक विस्तार करना 2-पैरामीटर विधि से एक साधारण प्रतिलिपि और पेस्ट है।

यह इल्जेन विधि से भी अधिक शक्तिशाली है क्योंकि आप अपने एसिंक पैटर्न में ईवेंट द्वारा बनाए गए किसी भी मूल्य का उपयोग कर सकते हैं।

// Empty events (Action style) 
static Task TaskFromEvent(object target, string eventName) { 
    var addMethod = target.GetType().GetEvent(eventName).GetAddMethod(); 
    var delegateType = addMethod.GetParameters()[0].ParameterType; 
    var tcs = new TaskCompletionSource<object>(); 
    var resultSetter = (Action)(() => tcs.SetResult(null)); 
    var d = Delegate.CreateDelegate(delegateType, resultSetter, "Invoke"); 
    addMethod.Invoke(target, new object[] { d }); 
    return tcs.Task; 
} 

// One-value events (Action<T> style) 
static Task<T> TaskFromEvent<T>(object target, string eventName) { 
    var addMethod = target.GetType().GetEvent(eventName).GetAddMethod(); 
    var delegateType = addMethod.GetParameters()[0].ParameterType; 
    var tcs = new TaskCompletionSource<T>(); 
    var resultSetter = (Action<T>)tcs.SetResult; 
    var d = Delegate.CreateDelegate(delegateType, resultSetter, "Invoke"); 
    addMethod.Invoke(target, new object[] { d }); 
    return tcs.Task; 
} 

// Two-value events (Action<T1, T2> or EventHandler style) 
static Task<Tuple<T1, T2>> TaskFromEvent<T1, T2>(object target, string eventName) { 
    var addMethod = target.GetType().GetEvent(eventName).GetAddMethod(); 
    var delegateType = addMethod.GetParameters()[0].ParameterType; 
    var tcs = new TaskCompletionSource<Tuple<T1, T2>>(); 
    var resultSetter = (Action<T1, T2>)((t1, t2) => tcs.SetResult(Tuple.Create(t1, t2))); 
    var d = Delegate.CreateDelegate(delegateType, resultSetter, "Invoke"); 
    addMethod.Invoke(target, new object[] { d }); 
    return tcs.Task; 
} 

उपयोग इस तरह होगा। जैसा कि आप देख सकते हैं, भले ही ईवेंट एक कस्टम प्रतिनिधि में परिभाषित किया गया हो, फिर भी यह काम करता है। और आप इवेंट किए गए मानों को टुपल के रूप में कैप्चर कर सकते हैं।

static async void Run() { 
    var result = await TaskFromEvent<int, string>(new MyClass(), "Fired"); 
    Console.WriteLine(result); // (123, "abcd") 
} 

public class MyClass { 
    public delegate void TwoThings(int x, string y); 

    public MyClass() { 
     new Thread(() => { 
      Thread.Sleep(1000); 
      Fired(123, "abcd"); 
     }).Start(); 
    } 

    public event TwoThings Fired; 
} 

Here's a helper function अगर ऊपर तीन तरीकों अपनी प्राथमिकताएँ के लिए अत्यधिक कॉपी और पेस्ट कर रहे हैं, आप केवल एक पंक्ति प्रत्येक में TaskFromEvent कार्यों लिखने के लिए अनुमति देंगे। मूल रूप से जो सरल था उसे सरल बनाने के लिए अधिकतम को क्रेडिट देना होगा।

+0

बहुत बहुत धन्यवाद !!! विंडोज फोन के लिए, इस लाइन को संशोधित किया जाना चाहिए: var पैरामीटर = methodInfo.GetParameters() चयन करें (ए => System.Linq.Expressions.Expression. पैरामीटर (ए। पैरामीटर टाइप, एनाम))। ToArray(); –

संबंधित मुद्दे