2013-08-17 4 views
25

मेरे पास मेरे समाधान में दो परियोजनाएं हैं: डब्ल्यूपीएफ परियोजना और कक्षा पुस्तकालय।कार्य करते समय वापसी आइटम कैसे प्राप्त करें। जब कोई

मेरी कक्षा पुस्तकालय में:

मैं प्रतीक की एक सूची है:

class Symbol 
{ 
    Identifier Identifier {get;set;} 
    List<Quote> HistoricalQuotes {get;set;} 
    List<Financial> HistoricalFinancials {get;set;} 
} 

प्रत्येक प्रतीक के लिए, मैं एक वित्तीय सेवा मेरी प्रतीकों में से हर एक के लिए ऐतिहासिक वित्तीय डेटा पुनः प्राप्त करने के लिए एक webrequest का उपयोग कर क्वेरी । (WebClient.DownloadStringTaskAsync (URI);)

तो यहाँ मेरी विधि है जो कि कार्य करें:

public async Task<IEnumerable<Symbol>> GetSymbolsAsync() 
    { 
     var historicalFinancialTask = new List<Task<HistoricalFinancialResult>>(); 

     foreach (var symbol in await _listSymbols) 
     { 
      historicalFinancialTask.Add(GetFinancialsQueryAsync(symbol)); 
     } 

     while (historicalFinancialTask.Count > 0) 
     { 
      var historicalFinancial = await Task.WhenAny(historicalFinancialTask); 
      historicalFinancialTask.Remove(historicalFinancial); 

      // the line below doesn't compile, which is understandable because method's return type is a Task of something 
      yield return new Symbol(historicalFinancial.Result.Symbol.Identifier, historicalFinancial.Result.Symbol.HistoricalQuotes, historicalFinancial.Result.Data); 
     } 
    } 

    private async Task<HistoricalFinancialResult> GetFinancialsQueryAsync(Symbol symbol) 
    { 
     var result = new HistoricalFinancialResult(); 
     result.Symbol = symbol; 
     result.Data = await _financialsQuery.GetFinancialsQuery(symbol.Identifier); // contains some logic like parsing and use WebClient to query asynchronously 
     return result; 
    } 

    private class HistoricalFinancialResult 
    { 
     public Symbol Symbol { get; set; } 
     public IEnumerable<Financial> Data { get; set; } 

     // equality members 
    } 

आप देख सकते हैं, मैं हर बार जब मैं प्रतीक प्रति एक वित्तीय ऐतिहासिक डेटा डाउनलोड कि, उपज के लिए चाहते हैं परिणाम देने के लिए वित्तीय सेवा के लिए मेरी सभी कॉल का इंतजार करने के बजाय परिणाम।

और मेरे WPF में, यहाँ मैं क्या करना चाहते हैं क्या है:

foreach(var symbol in await _service.GetSymbolsAsync()) 
{ 
     SymbolsObservableCollection.Add(symbol); 
} 

ऐसा लगता है कि हम एक async विधि में वापसी उपज नहीं कर सकते तो क्या समाधान मैं उपयोग कर सकते हैं? मेरे WPF प्रोजेक्ट में मेरी GetSymbols विधि को स्थानांतरित करने के अलावा।

+0

Btw, पूरा होने के अपने आदेश से 'Task's का एक संग्रह पर कार्रवाई करने के, पर एक नजर है [' OrderByCompletion() '] (http://nitoasyncex.codeplex.com/wikipage?title=TaskExtensions&referringTitle=Documentation) से निटो AsyncEx। – svick

+0

आप [रिएक्टिव एक्सटेंशन (आरएक्स)] को देखा है (http://msdn.microsoft.com/en-us/data/gg577609.aspx)? –

+0

मेरे पास है, लेकिन अस्तर वक्र मेरे लिए बहुत अधिक है और वास्तव में मुझे समझ नहीं आता क्या माइक्रोसॉफ्ट इस प्रौद्योगिकी के भविष्य पर विचार कर रहा है क्योंकि वे भी TPL Dataflow जो एक ही काम करते हैं थोड़े लगता है। जिस समस्या में मैं यहां पूछ रहा हूं वह इतना आसान है, इसलिए समाधान भी सरल होना चाहिए। और मुझे अभी भी समझ में नहीं आ रहा है कि क्यों वे .NET Framework में आरएक्स या डेटाफ्लो शामिल नहीं करते हैं, ऐसा लगता है कि वे इन दो ढांचे को भी शामिल करने के लिए पर्याप्त भरोसा नहीं करते हैं। – Gui

उत्तर

39

है कि के साथ, अपने कोड ऐसा दिखाई दे सकता (मैं भी बेहतर नाम के लिए कुछ चर को बदल दिया है) मुझे टीपीएल डेटाफ्लो घटकों (जो svick आपको उपयोग करने का सुझाव देता है) पसंद करते हैं, उस सिस्टम पर जाने के लिए पर्याप्त प्रतिबद्धता की आवश्यकता होती है - यह ऐसा कुछ नहीं है जिसे आप किसी मौजूदा डिज़ाइन में जोड़ सकते हैं। यदि आप सीपीयू-गहन डेटा प्रोसेसिंग के उच्च वॉल्यूम कर रहे हैं और कई सीपीयू कोर का फायदा उठाना चाहते हैं तो यह काफी लाभ प्रदान करता है। लेकिन इसमें से सर्वश्रेष्ठ प्राप्त करना गैर-तुच्छ है।

आरएक्स का उपयोग करके उनका अन्य सुझाव, मौजूदा समाधान के साथ एकीकृत करना आसान हो सकता है। (original documentation देखें, लेकिन नवीनतम कोड के लिए, Rx-Main nuget पैकेज का उपयोग करें। या यदि आप स्रोत को देखना चाहते हैं, तो the Rx CodePlex site देखें) यदि आप चाहते हैं तो IEnumerable<Symbol> का उपयोग करके कॉलिंग कोड को जारी रखना भी संभव होगा - आप एक कार्यान्वयन विस्तार के रूप में पूरी तरह से आरएक्स का उपयोग कर सकते हैं, [2013/11/09 को संपादित करने के लिए:] हालांकि, जैसा कि svick ने इंगित किया है, शायद यह आपके अंतिम लक्ष्य को देखते हुए शायद एक अच्छा विचार नहीं है।

इससे पहले कि मैं आपको एक उदाहरण दिखाने के लिए, मैं वास्तव में क्या हम कर रहे हैं के बारे में स्पष्ट होना चाहता हूँ। आपका उदाहरण इस हस्ताक्षर के साथ एक विधि था: "। यह एक तरीका है कि प्रकार IEnumerable<Symbol> की एक एकल परिणाम पैदा करता है, और यह कि परिणाम तुरंत उत्पादन नहीं कर सकते हैं"

public async Task<IEnumerable<Symbol>> GetSymbolsAsync() 

कि वापसी प्रकार, Task<IEnumerable<Symbol>>, अनिवार्य रूप से कहते

यह एकल परिणाम बिट है जो मुझे लगता है कि आपको दुःख पैदा हो रहा है, क्योंकि यह वास्तव में आप नहीं चाहते हैं। Task<T> (कोई फर्क नहीं पड़ता कि T हो सकता है) एक एकल असीमित ऑपरेशन का प्रतिनिधित्व करता है। इसमें कई कदम हो सकते हैं (await के कई उपयोग यदि आप इसे C# async विधि के रूप में कार्यान्वित करते हैं) लेकिन आखिरकार यह एक चीज़ उत्पन्न करता है। आप कई चीजें, अलग-अलग, समय पर उत्पादन करना चाहते हैं, इसलिए Task<T> एक अच्छा फिट नहीं है।

यदि आप वास्तव में ऐसा करने जा रहे थे जो आपके विधि हस्ताक्षर का वादा करता है - अंततः एक परिणाम उत्पन्न करना - एक तरीका यह है कि आप ऐसा कर सकते हैं कि आपकी एसिंक विधि एक सूची बनाएं और उसके बाद परिणाम के रूप में अच्छा और तैयार हो,

// Note: this first example is *not* what you want. 
// However, it is what your method's signature promises to do. 
public async Task<IEnumerable<Symbol>> GetSymbolsAsync() 
{ 
    var historicalFinancialTask = new List<Task<HistoricalFinancialResult>>(); 

    foreach (var symbol in await _listSymbols) 
    { 
     historicalFinancialTask.Add(GetFinancialsQueryAsync(symbol)); 
    } 

    var results = new List<Symbol>(); 
    while (historicalFinancialTask.Count > 0) 
    { 
     var historicalFinancial = await Task.WhenAny(historicalFinancialTask); 
     historicalFinancialTask.Remove(historicalFinancial); 

     results.Add(new Symbol(historicalFinancial.Result.Symbol.Identifier, historicalFinancial.Result.Symbol.HistoricalQuotes, historicalFinancial.Result.Data)); 
    } 

    return results; 
} 

इस विधि क्या करता है उसके हस्ताक्षर का कहना है: यह एसिंक्रोनस रूप प्रतीकों में से एक दृश्य पैदा करता है।

लेकिन संभवतः आप IEnumerable<Symbol> बनाना चाहते हैं जो आइटम उपलब्ध होने के बावजूद आइटम उपलब्ध कराते हैं, जब तक कि वे सभी उपलब्ध न हों। (अन्यथा, आप शायद WhenAll का उपयोग कर सकते हैं।) आप ऐसा कर सकते हैं, लेकिन yield return कोई रास्ता नहीं है।

संक्षेप में, मुझे लगता है कि आप क्या करना चाहते हैं वह एक असीमित सूची उत्पन्न करता है। वहाँ उस के लिए एक प्रकार है: IObservable<T> व्यक्त करता है मैं वास्तव में क्या लगता है कि आप अपने Task<IEnumerable<Symbol>> साथ व्यक्त करने के लिए उम्मीद कर रहे थे: यह आइटम (बस IEnumerable<T> की तरह), लेकिन अतुल्यकालिक का एक क्रम है।

यह सादृश्य द्वारा यह समझने में मदद कर सकते हैं:

public IObservable<Symbol> GetSymbolsObservable() ... 
:

public Symbol GetSymbol() ... 

को

public Task<Symbol> GetSymbolAsync() ... 

रूप

public IEnumerable<Symbol> GetSymbols() ... 

है है

(दुर्भाग्यवश, Task<T> के विपरीत, एक असीमित अनुक्रम-उन्मुख विधि को कॉल करने के लिए एक सामान्य नामकरण सम्मेलन नहीं है। मैंने अंत में 'अवलोकन' जोड़ा है, लेकिन यह सार्वभौमिक अभ्यास नहीं है। । मैं निश्चित रूप से यह GetSymbolsAsync फोन नहीं है, क्योंकि लोगों को वापस जाने के लिए उम्मीद करेंगे एक Task)

दूसरे शब्दों में कहें करने के लिए, Task<IEnumerable<T>> कहते हैं जबकि IObservable<T> कहते हैं, "मैं इस संग्रह जब मैं अच्छा और तैयार हूँ उत्पादन होगा": "यहां एक संग्रह है। जब मैं अच्छा और तैयार हूं तो मैं प्रत्येक आइटम का उत्पादन करूंगा।"

तो, आप के लिए एक विधि है कि Symbol वस्तुओं, जहां उन वस्तुओं एसिंक्रोनस रूप से उत्पादित कर रहे हैं का एक अनुक्रम रिटर्न चाहते हैं। यह हमें बताता है कि आपको वास्तव में IObservable<Symbol> लौटाना चाहिए।यहाँ एक कार्यान्वयन है:

// Unlike this first example, this *is* what you want. 
public IObservable<Symbol> GetSymbolsRx() 
{ 
    return Observable.Create<Symbol>(async obs => 
    { 
     var historicalFinancialTask = new List<Task<HistoricalFinancialResult>>(); 

     foreach (var symbol in await _listSymbols) 
     { 
      historicalFinancialTask.Add(GetFinancialsQueryAsync(symbol)); 
     } 

     while (historicalFinancialTask.Count > 0) 
     { 
      var historicalFinancial = await Task.WhenAny(historicalFinancialTask); 
      historicalFinancialTask.Remove(historicalFinancial); 

      obs.OnNext(new Symbol(historicalFinancial.Result.Symbol.Identifier, historicalFinancial.Result.Symbol.HistoricalQuotes, historicalFinancial.Result.Data)); 
     } 
    }); 
} 

आप देख सकते हैं, यह आप काफी बारे में क्या आप लिखने के लिए उम्मीद कर रहे थे की सुविधा देता है - इस कोड के शरीर तुम्हारा के लगभग समान है। केवल अंतर यह है कि आप yield return (जो संकलित नहीं किया गया था) का उपयोग कर रहे थे, यह आरएक्स द्वारा प्रदान की गई वस्तु पर OnNext विधि को कॉल करता है।

लिखा करने के बाद, आप आसानी से एक IEnumerable<Symbol> में इस लपेट कर सकते हैं ([जोड़ने के लिए संपादित 2013/11/29:] हालांकि आप शायद नहीं वास्तव में ऐसा करना चाहते हैं - जवाब के अंत में इसके अलावा देखें):

public IEnumerable<Symbol> GetSymbols() 
{ 
    return GetSymbolsRx().ToEnumerable(); 
} 

यह अतुल्यकालिक नहीं लग सकता है, लेकिन यह वास्तव में अंतर्निहित कोड एसिंक्रोनस रूप से संचालित करने के लिए अनुमति नहीं है। जब आप इस विधि को कॉल करते हैं, तो यह अवरुद्ध नहीं होगा - भले ही अंतर्निहित कोड जो वित्तीय जानकारी लाने का काम करता है, तुरंत परिणाम नहीं दे सकता है, फिर भी यह विधि तुरंत IEnumerable<Symbol> लौटाएगी। अब निश्चित रूप से, कोई भी कोड जो उस संग्रह के माध्यम से पुन: प्रयास करने का प्रयास करता है, यदि डेटा अभी तक उपलब्ध नहीं है तो अवरुद्ध हो जाएगा।

  • आपको लगता है कि Observable.Create<T> को काम (मेरे उदाहरण में एक प्रतिनिधि, एक तर्क के रूप में पारित करता है एक async विधि लिखने के लिए मिलता है, लेकिन आप कर सकते थे: लेकिन महत्वपूर्ण बात यह है कि मैं क्या लगता है कि आप मूल रूप से प्राप्त करने के लिए कोशिश कर रहे थे करता है यदि आप चाहें तो एक स्टैंडअलोन async विधि लिखना)
  • बुला कोड प्रतीकों लाना प्रारंभ करने के लिए कहेगा का परिणाम के रूप में केवल अवरुद्ध नहीं किया जाएगा
  • IEnumerable<Symbol> जिसके परिणामस्वरूप जैसे ही यह उपलब्ध हो जाता है प्रत्येक व्यक्ति के मद का उत्पादन करेगा

यह काम करता है क्योंकि आरएक्स की ToEnumerable विधि में कुछ चालाक कोड है जो IEnumerable<T> के सिंक्रोनस वर्ल्ड व्यू और परिणामों के असीमित उत्पादन के बीच अंतर को पुल करता है। (दूसरे शब्दों में, यह ठीक है कि आप सी # को खोजने में निराश थे, जो आपके लिए करने में सक्षम नहीं था।)

यदि आप उत्सुक हैं, तो आप स्रोत को देख सकते हैं। कोड है कि underlies क्या ToEnumerable करता https://rx.codeplex.com/SourceControl/latest#Rx.NET/Source/System.Reactive.Linq/Reactive/Linq/Observable/GetEnumerator.cs

में पाया जा सकता [संपादित 2013/11/29 जोड़ने के लिए:]

svick टिप्पणियां कुछ मैं याद में बताया गया है: अपने अंतिम लक्ष्य डाल करने के लिए है ObservableCollection<Symbol> में सामग्री। किसी तरह मैंने उस बिट को नहीं देखा। इसका मतलब है कि IEnumerable<T> जाने का गलत तरीका है - आप foreach लूप के साथ करने के बजाय, आइटम को उपलब्ध होने के रूप में संग्रह को पॉप्युलेट करना चाहते हैं। तो आप बस यह करेंगे:

GetSymbolsRx().Subscribe(symbol => SymbolsObservableCollection.Add(symbol)); 

या उन पंक्तियों के साथ कुछ। इससे संग्रह में वस्तुओं को और जब वे उपलब्ध हो जाएंगे।

यह यूआई थ्रेड पर पूरी तरह से लात मारने पर निर्भर करता है। जब तक यह है, आपका एसिंक कोड UI थ्रेड पर चलना समाप्त हो जाना चाहिए, जिसका अर्थ है कि जब संग्रह में आइटम जोड़े जाते हैं, तो यह यूआई थ्रेड पर भी होता है।लेकिन अगर किसी कारण से आप एक कार्यकर्ता धागे से बातें शुरू करने अंत (या आप प्रतीक्षा कर रहा है में से किसी पर ConfigureAwait उपयोग करने के लिए, इस प्रकार यूआई धागा के सिलसिले को तोड़ने के थे तो) आप आरएक्स से आइटम को संभालने के लिए व्यवस्था करने के लिए आवश्यकता होगी सही धागे पर धारा:

GetSymbolsRx() 
    .ObserveOnDispatcher() 
    .Subscribe(symbol => SymbolsObservableCollection.Add(symbol)); 

आप यूआई धागे पर कर रहे हैं जब आप करते हैं कि, यह वर्तमान डिस्पैचर लेने आऊँगा, और यह सुनिश्चित सभी सूचनाएं इसके माध्यम से आते हैं तो। यदि आप सदस्यता लेने के लिए पहले से ही गलत धागे पर हैं, तो आप ObserveOn अधिभार का उपयोग कर सकते हैं जो एक प्रेषक लेता है। (ये System.Reactive.Windows.Threading के लिए एक संदर्भ करने का आग्रह कर और इन विस्तार तरीके हैं, तो आप उनके युक्त नाम स्थान है, जो भी System.Reactive.Windows.Threading कहा जाता है के लिए एक using आवश्यकता होगी।)

+4

जबकि मैं अधिकतर उत्तर से सहमत हूं, मुझे लगता है कि 'ToEnumerable() 'यहां सही समाधान नहीं है। प्रश्न के मुताबिक, अंतिम लक्ष्य 'ऑब्जर्वबेल कोलेक्शन' भरना है क्योंकि आइटम उपलब्ध हो जाते हैं। चूंकि आम तौर पर आपको यूआई थ्रेड से ऐसा करना होता है, 'ToEnumerable()' का उपयोग करके आप पूरे समय यूआई थ्रेड को अवरुद्ध कर सकते हैं। – svick

+1

पता नहीं कैसे मुझे याद आया - धन्यवाद। मैंने इसे आपकी टिप्पणी के प्रकाश में संपादित किया है। आपके अच्छे लिखित उत्तर और ब्लॉग के लिए –

+1

+1। इस तरह stacoverflow मुझे प्रेरित करता है :) –

0

मेरा मानना ​​है कि आप async विधि भी एक इटरेटर विधि होने में सक्षम नहीं हैं। यह .NET की एक सीमा है। Task Parallel Library Dataflow का उपयोग करने पर एक नज़र डालें, इसका उपयोग डेटा को संसाधित करने के लिए किया जा सकता है क्योंकि यह उपलब्ध हो जाता है। और Reactive Extensions.

6

जो आप पूछ रहे हैं वह अधिक समझ में नहीं आता है, क्योंकि IEnumerable<T> एक तुल्यकालिक इंटरफ़ेस है। दूसरे शब्दों में, यदि कोई आइटम अभी तक उपलब्ध नहीं है, तो MoveNext() विधि को अवरुद्ध करना होगा, इसकी कोई अन्य विकल्प नहीं है।

आपको IEnumerable<T> के कुछ प्रकार के असीमित संस्करण की आवश्यकता है। इसके लिए, आप TPL dataflow से आरएक्स या (मेरे पसंदीदा) ब्लॉक से IObservable<T> का उपयोग कर सकते हैं।

public IReceivableSourceBlock<Symbol> GetSymbolsAsync() 
{ 
    var block = new BufferBlock<Symbol>(); 

    GetSymbolsAsyncCore(block).ContinueWith(
     task => ((IDataflowBlock)block).Fault(task.Exception), 
     TaskContinuationOptions.NotOnRanToCompletion); 

    return block; 
} 

private async Task GetSymbolsAsyncCore(ITargetBlock<Symbol> block) 
{ 
    // snip 

    while (historicalFinancialTasks.Count > 0) 
    { 
     var historicalFinancialTask = 
      await Task.WhenAny(historicalFinancialTasks); 
     historicalFinancialTasks.Remove(historicalFinancialTask); 
     var historicalFinancial = historicalFinancialTask.Result; 

     var symbol = new Symbol(
      historicalFinancial.Symbol.Identifier, 
      historicalFinancial.Symbol.HistoricalQuotes, 
      historicalFinancial.Data); 

     await block.SendAsync(symbol); 
    } 
} 

और उपयोग किया जा सकता है:

var symbols = _service.GetSymbolsAsync(); 
while (await symbols.OutputAvailableAsync()) 
{ 
    Symbol symbol; 
    while (symbols.TryReceive(out symbol)) 
     SymbolsObservableCollection.Add(symbol); 
} 

या:

var symbols = _service.GetSymbolsAsync(); 
var addToCollectionBlock = new ActionBlock<Symbol>(
    symbol => SymbolsObservableCollection.Add(symbol)); 
symbols.LinkTo(
    addToCollectionBlock, new DataflowLinkOptions { PropagateCompletion = true }); 
await symbols.Completion; 
+0

आपके उत्तर के लिए धन्यवाद। मैंने कभी भी टीपीएल डेटाफ्लो और आरएक्स में गहराई से जाने का समय नहीं लिया है जिसे मैंने केवल नाम से सुना है। जब मैं टीपीएल डेटाफ्लो का उपयोग करके प्रदान किए गए कोड का उदाहरण पढ़ता हूं तो मैं पूरी तरह से खो जाता हूं। क्या आप टीपीएल डेटाफ्लो के साथ गति प्राप्त करने के लिए मुझे एक वेबसाइट या कोई पुस्तक सुझा सकते हैं? मैंने इस पर कुछ शोध किया है, और यह ढांचा केवल 1 साल से बाहर रहा है, मुझे डर है कि मुझे बहुत सारे दस्तावेज नहीं मिलेगा जब मैं इसके साथ कुछ करने के लिए फंस जाऊंगा। क्या आपको पता है कि यह .NET Framework में क्यों शामिल नहीं है? – Gui

+0

@ गुई इसे सीधे ढांचे में शामिल नहीं किया गया है, ताकि इसे इससे अधिक बार अपडेट किया जा सके। – svick

+0

मुझे लगता है कि आप का मतलब 'GetSymbolsAsyncCore (ब्लॉक)' 'GetSymbolsAsyncCore()। ContinueWith (...)' के बजाय .ContinueWith (...)। एक और नाइटपिक शायद उन तरीकों के लिए Async प्रत्यय का उपयोग नहीं करता है जो स्वयं एसिंक नहीं हैं लेकिन एक डेटा संरचना लौटाती है जो असीमित रूप से बदलती रहती है। Async प्रत्यय का उपयोग करना एक इंप्रेशन देता है कि कीवर्ड का इंतजार उस विधि पर लागू होता है। – ShitalShah

0

क्यों कुछ इस तरह से काम नहीं:

public async IEnumerable<Task<Symbol>> GetSymbolsAsync() 
{ 
    var historicalFinancialTask = new List<Task<HistoricalFinancialResult>>(); 

    foreach (var symbol in await _listSymbols) 
    { 
     historicalFinancialTask.Add(GetFinancialsQueryAsync(symbol)); 
    } 

    while (historicalFinancialTask.Count > 0) 
    { 
     var historicalFinancial = await Task.WhenAny(historicalFinancialTask); 
     historicalFinancialTask.Remove(historicalFinancial); 

     yield return new Symbol(historicalFinancial.Result.Symbol.Identifier, historicalFinancial.Result.Symbol.HistoricalQuotes, historicalFinancial.Result.Data); 
    } 
} 
+0

यह काम करता है, लेकिन कुछ समस्याएं हैं। यह एसिंक काम को सभी सामने सामने भेजता है, भले ही कोई भी संग्रह को गणना करता है या नहीं, और यह उसी समय एसिंक कार्य को प्रेषित करता है (जो डेटा के स्रोत के आधार पर अनुपयुक्त हो सकता है)। एक आरएक्स आधारित दृष्टिकोण के साथ पहली समस्या दूर हो जाती है, और दूसरा पसंद का मामला है। – piers7

+0

क्या यह काम करता है? मुझे लगता है जैसे GetSymbolsAsync को एक कार्य वापस करना है – Vidar

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