2010-08-22 14 views
31

मैं सीखने की कोशिश कर रहा हूं कि कैसे async और let! F # में काम करते हैं। मेरे द्वारा पढ़े गए सभी दस्तावेज़ भ्रमित लगते हैं। Async.RunSynchronously के साथ एक async ब्लॉक चलाने का क्या मतलब है? क्या यह एसिंक या सिंक है? एक विरोधाभास की तरह लग रहा है।एफ # एसिंक वास्तव में कैसे काम करता है?

प्रलेखन का कहना है कि Async.StartImmediate वर्तमान धागे में चलता है। यदि यह एक ही धागे में चलता है, तो यह मेरे लिए बहुत अतुल्यकालिक नहीं दिखता है ... या हो सकता है कि asyncs धागे की बजाय coroutines की तरह अधिक हो। यदि हां, तो वे आगे कैसे आते हैं?

का हवाला देते हुए एमएस डॉक्स:

कोड की पंक्ति जाने का उपयोग करता है! गणना शुरू होती है, और फिर परिणाम उपलब्ध होने तक थ्रेड को निलंबित कर दिया जाता है, जिस बिंदु पर निष्पादन जारी रहता है।

यदि थ्रेड परिणाम के लिए इंतजार कर रहा है, तो मुझे इसका उपयोग क्यों करना चाहिए? सादे पुराने फ़ंक्शन कॉल की तरह दिखता है।

और Async.Parallel क्या करता है? यह Async < 'टी> का अनुक्रम प्राप्त करता है। समानांतर में सादे कार्यों का अनुक्रम क्यों नहीं किया जा सकता है?

मुझे लगता है कि मुझे यहां कुछ मूलभूत याद आ रही है। मुझे लगता है कि मैं समझने के बाद, सभी दस्तावेज और नमूने समझना शुरू कर देंगे।

+0

http://stackoverflow.com/questions/2444676/understanding-f-asynchronous-programming –

उत्तर

30

कुछ चीजें।

पहले,

let resp = req.GetResponse() 

और

let! resp = req.AsyncGetReponse() 

के बीच अंतर यह है कि शायद (सीपीयू के लिए एक अनंत काल) मिलीसेकेंड के सैकड़ों जहां वेब अनुरोध समुद्र में 'है, के लिए पूर्व एक थ्रेड (I/O पर अवरुद्ध) का उपयोग कर रहा है, जबकि बाद में शून्य धागे का उपयोग कर रहा है। यह एसिंक के लिए सबसे आम 'जीत' है: आप गैर-अवरुद्ध I/O लिख सकते हैं जो हार्ड डिस्क के चारों ओर घूमने या नेटवर्क अनुरोधों को वापस करने के लिए प्रतीक्षा करने वाले किसी भी थ्रेड को बर्बाद नहीं करता है। (सबसे अन्य भाषाओं के विपरीत, आप कॉलबैक में नियंत्रण और कारक चीजों के उलट करने को मजबूर नहीं कर रहे हैं।)

दूसरा, Async.StartImmediate वर्तमान धागे पर एक async शुरू कर देंगे। एक सामान्य उपयोग एक जीयूआई के साथ है, आपके पास कुछ जीयूआई ऐप है जो उदा। यूआई अपडेट करें (उदाहरण के लिए कहीं "लोडिंग ..." कहें), और उसके बाद कुछ पृष्ठभूमि कार्य करें (डिस्क या जो भी कुछ लोड करें), और फिर पूर्ण होने पर UI को अपडेट करने के लिए अग्रभूमि UI थ्रेड पर वापस आएं ("किया गया!")।StartImmediate ऑपरेशन की शुरुआत में यूआई को अपडेट करने और SynchronizationContext पर कब्जा करने के लिए एसिंक को सक्षम बनाता है ताकि ऑपरेशन के अंत में यूआई के अंतिम अपडेट करने के लिए जीयूआई पर वापस जा सके।

अगला, Async.RunSynchronously शायद ही कभी उपयोग किया जाता है (एक थीसिस यह है कि आप इसे किसी भी ऐप में सबसे अधिक बार कॉल करते हैं)। सीमा में, यदि आपने अपना संपूर्ण प्रोग्राम एसिंक लिखा है, तो "मुख्य" विधि में आप प्रोग्राम चलाने के लिए RunSynchronously पर कॉल करेंगे और परिणाम की प्रतीक्षा करेंगे (उदाहरण के लिए एक कंसोल ऐप में परिणाम प्रिंट करने के लिए)। यह थ्रेड को अवरुद्ध करता है, इसलिए यह आमतौर पर सिंक सामग्री के साथ सीमा पर, आपके प्रोग्राम के एसिंक भाग के बहुत ही 'शीर्ष' पर उपयोगी होता है। (और अधिक उन्नत उपयोगकर्ता पसंद कर सकते हैं StartWithContinuations - RunSynchronously किस्म की "आसान हैक" वापस सिंक करने के लिए async से प्राप्त करने के लिए है।)

अंत में, Async.Parallel करता समानांतरवाद कांटा-में शामिल हो। आप एक समान कार्य लिख सकते हैं जो async एस (टीपीएल में सामान की तरह) के बजाय कार्यों को लेता है, लेकिन एफ # में सामान्य मीठा स्थान समानांतर I/O- बाध्य गणना है, जो पहले से ही एसिंक ऑब्जेक्ट्स हैं, इसलिए यह सबसे सामान्य है उपयोगी हस्ताक्षर। (सीपीयू-बाध्य समांतरता के लिए, आप एसिंक का उपयोग कर सकते हैं, लेकिन आप टीपीएल का भी उपयोग कर सकते हैं।)

+1

धन्यवाद! आपके पोस्ट में उल्लिखित प्रमुख शब्दों के लिए सर्चिंग, मुझे मिला है http://blogs.msdn.com/b/dsyme/archive/2009/10/19/release-notes-for-the-f-october-2009- release.aspx कि कुछ परिचालनों में "संदर्भ में स्वत: वापसी" होती है, इसलिए जीयूआई संचालन को कार्यान्वित करना आसान होता है। लेकिन अगर प्रोग्रामर इस सुविधा से अनजान है, तो यह बहुत भ्रमित हो जाता है, क्योंकि पहली नजर में कोड टूटा हुआ प्रतीत होता है और फिर भी यह काम करता है। – marcus

+1

हां, आम मामला और कोड/थ्रेडिंग मॉडल की समग्र परिप्रेक्ष्य/पारदर्शिता में सुविधा के बीच यहां निश्चित रूप से एक व्यापार-बंद है। – Brian

+1

सिर्फ एक कथन पर एक टिप्पणी है कि 'StartImmediate'' सिंक्रनाइज़ेशन कनेक्ट 'के कब्जे को सक्षम बनाता है। यह कैप्चर को पूरी तरह से सक्षम नहीं करता है, लेकिन उपयोगकर्ता को गणना अभिव्यक्ति के भाग सिंक्रोनस भाग में सिंक्रनाइज़ेशन संदर्भ सेट करने की अनुमति देता है, और उसके बाद बाद में UI थ्रेड पर वापस जाने के लिए 'Async.SwitchToContext (syncContext)' जैसे कुछ करता है। – kasperhj

12

एसिंक का उपयोग उपयोग में धागे की संख्या को बचाने के लिए है।

निम्नलिखित उदाहरण देखें: एक समारोह को परिभाषित करने और समानांतर में निष्पादित करें:

let fetchUrlSync url = 
    let req = WebRequest.Create(Uri url) 
    use resp = req.GetResponse() 
    use stream = resp.GetResponseStream() 
    use reader = new StreamReader(stream) 
    let contents = reader.ReadToEnd() 
    contents 

let sites = ["http://www.bing.com"; 
      "http://www.google.com"; 
      "http://www.yahoo.com"; 
      "http://www.search.com"] 

// execute the fetchUrlSync function in parallel 
let pagesSync = sites |> PSeq.map fetchUrlSync |> PSeq.toList 

ऊपर कोड क्या आप के लिए क्या करना चाहते हैं। तो हमें यहां एसिंक क्यों चाहिए?

चलिए कुछ बड़ा मानते हैं। जैसे यदि साइटों की संख्या 4 नहीं है, लेकिन कहें, 10,000! फिर उन्हें समानांतर में चलाने के लिए 10,000 धागे की आवश्यकता होती है, जो एक विशाल संसाधन लागत है। async में

जबकि:

let fetchUrlAsync url = 
    async { let req = WebRequest.Create(Uri url) 
      use! resp = req.AsyncGetResponse() 
      use stream = resp.GetResponseStream() 
      use reader = new StreamReader(stream) 
      let contents = reader.ReadToEnd() 
      return contents } 
let pagesAsync = sites |> Seq.map fetchUrlAsync |> Async.Parallel |> Async.RunSynchronously 

जब कोड use! resp = req.AsyncGetResponse() में है, वर्तमान धागा छोड़ दिया जाता है और अपने संसाधन अन्य प्रयोजनों के लिए इस्तेमाल किया जा सकता। अगर प्रतिक्रिया 1 सेकंड में वापस आती है, तो आपका धागा अन्य सामानों को संसाधित करने के लिए इस 1 सेकंड का उपयोग कर सकता है। अन्यथा थ्रेड अवरुद्ध है, 1 सेकंड के लिए थ्रेड संसाधन बर्बाद कर रहा है।

तो यहां तक ​​कि आप असीमित तरीके से समानांतर में 10000 वेब पेज डाउनलोड कर रहे हैं, धागे की संख्या एक छोटी संख्या तक ही सीमित है।

मुझे लगता है कि आप .NET/C# प्रोग्रामर नहीं हैं। एसिंक ट्यूटोरियल आमतौर पर मानता है कि कोई जानता है .NET और सी # (बहुत सारे कोड) में एसिंक्रोनस आईओ प्रोग्राम कैसे करें। एफ # में Async निर्माण का जादू समानांतर के लिए नहीं है। क्योंकि सरल समांतर अन्य संरचनाओं द्वारा महसूस किया जा सकता है, उदा। नेट समांतर विस्तार में समानांतर। हालांकि, एसिंक्रोनस आईओ अधिक जटिल है, क्योंकि आप देखते हैं कि धागा अपना निष्पादन छोड़ देता है, जब आईओ खत्म होता है, आईओ को अपने मूल धागे को जागने की जरूरत होती है। यह वह जगह है जहां एसिंक जादू का प्रयोग किया जाता है: संक्षिप्त कोड की कई पंक्तियों में, आप बहुत जटिल नियंत्रण कर सकते हैं।

+0

मुझे नहीं लगता कि आपने सही प्रश्न का उत्तर दिया है। – Gabe

+0

मुझे लगता है कि मैं सही हूँ। मैंने अपने प्रश्न से शुरुआत की "क्यों समानांतर में सादा कार्यों का अनुक्रम निष्पादित नहीं किया जा सकता है?"। तो मैं भविष्यवाणी करता हूं कि वह नेट के लिए नया है, इस प्रकार एसिंक आईओ की प्रेरणा को देखने की जरूरत है। –

+1

.NET और समांतर फ्रेमवर्क एक्सटेंशन लाइब्रेरी में नए होने के बीच एक अंतर है, और यह प्रश्न बाद के अधिक है। –

1

let! और Async.RunSynchronously के पीछे विचार यह है कि कभी-कभी आपके पास एक असीमित गतिविधि होती है जिसे आप जारी रखने से पहले परिणामों की आवश्यकता होती है। उदाहरण के लिए, "एक वेब पेज डाउनलोड करें" फ़ंक्शन में सिंक्रोनस समतुल्य नहीं हो सकता है, इसलिए आपको इसे सिंक्रनाइज़ करने के लिए कुछ तरीके चाहिए। या यदि आपके पास Async.Parallel है, तो आपके पास समवर्ती रूप से सभी सैकड़ों कार्य हो सकते हैं, लेकिन आप उन्हें जारी रखने से पहले पूरा करना चाहते हैं।

जहाँ तक मैं कह सकता हूं, Async.StartImmediate का उपयोग करने का कारण यह है कि आपके पास कुछ गणना है जिसे आपको मौजूदा थ्रेड (शायद एक यूआई थ्रेड) पर अवरुद्ध किए बिना चलाने की आवश्यकता है। क्या यह कोरआउट का उपयोग करता है? मुझे लगता है कि आप इसे कॉल कर सकते हैं, हालांकि .NET में सामान्य कोरआउट तंत्र नहीं है।

तो Async.Parallel को Async<'T> का अनुक्रम क्यों चाहिए? शायद क्योंकि यह Async<'T> ऑब्जेक्ट्स लिखने का एक तरीका है। आप आसानी से अपने स्वयं के अमूर्तता को बना सकते हैं जो केवल सादा कार्यों (या सादा कार्यों का संयोजन और Async एस का संयोजन है, लेकिन यह केवल एक सुविधा समारोह होगा।

0

एसिंक ब्लॉक में आप कुछ तुल्यकालिक और कुछ एसिंक ऑपरेशंस कर सकते हैं , इसलिए, उदाहरण के लिए, आपके पास ऐसी वेबसाइट हो सकती है जो उपयोगकर्ता की स्थिति को कई तरीकों से दिखाएगी, ताकि आप दिखा सकें कि उनके पास बिल हैं जो जल्द ही देय हैं, जन्मदिन आ रहे हैं और होमवर्क दे रहे हैं। इनमें से कोई भी नहीं है एक ही डेटाबेस, इसलिए आपका आवेदन तीन अलग-अलग कॉल करेगा। आप कॉल को समानांतर में बनाना चाहते हैं, ताकि जब सबसे धीमा हो, तो आप परिणाम एक साथ रख सकते हैं और इसे प्रदर्शित कर सकते हैं, इसलिए, अंतिम परिणाम यह होगा कि प्रदर्शन सबसे धीमा पर आधारित है। आपको इस बात की परवाह नहीं है कि ये वापस आते हैं, आप बस जानना चाहते हैं कि सभी तीन कब प्राप्त होते हैं।

मेरा उदाहरण पूरा करने के लिए, फिर आप इस जानकारी को दिखाने के लिए यूआई बनाने के लिए सिंक्रनाइज़ रूप से काम करना चाहते हैं। तो, अंत में, आप चाहते थे कि यह डेटा लाया गया हो और यूआई प्रदर्शित हो, ऐसे हिस्सों जहां ऑर्डर कोई फर्क नहीं पड़ता है समानांतर में किया जाता है, और जहां सिंक्रोनस फैशन में ऑर्डर मामलों को किया जा सकता है।

आप इन्हें तीन धागे के रूप में कर सकते हैं, लेकिन फिर तीसरे व्यक्ति को समाप्त होने पर आपको मूल थ्रेड ट्रैक करना और रोकना होगा, लेकिन यह अधिक काम है, .NET ढांचे का ध्यान रखना आसान है। ।

5

हाल ही में मैंने Async मॉड्यूल में कार्यों का एक संक्षिप्त अवलोकन किया है: here। शायद यह मदद करेगा।

+0

बहुत उपयोगी, धन्यवाद! लेकिन मुझे यह सब कुछ समझने में कुछ समय लगेगा! :-) – marcus

8

यहां कई अच्छे उत्तरों हैं लेकिन मैंने सोचा कि मैं प्रश्न के लिए एक अलग कोण लेता हूं: एफ # एसिंक वास्तव में कैसे काम करता है?

सी # एफ # डेवलपर्स में async/await के विपरीत वास्तव में Async का अपना संस्करण लागू कर सकते हैं। Async कैसे काम करता है यह जानने का यह एक शानदार तरीका हो सकता है।

(Async दिलचस्पी स्रोत कोड के लिए यहां पाया जा सकता: https://github.com/Microsoft/visualfsharp/blob/fsharp4/src/fsharp/FSharp.Core/control.fs)

हमारे DIY workflows के लिए

हमारे मौलिक निर्माण खंड के रूप में हम परिभाषित:

type DIY<'T> = ('T->unit)->unit 

यह एक समारोह है कि एक और स्वीकार करता है फ़ंक्शन (निरंतरता कहा जाता है) जिसे तब कहा जाता है जब 'T का परिणाम तैयार है। यह कॉलिंग थ्रेड को अवरुद्ध किए बिना पृष्ठभूमि कार्य शुरू करने के लिए DIY<'T> की अनुमति देता है। जब परिणाम तैयार होता है तो निरंतरता को गणना जारी रखने की अनुमति कहा जाता है।

एफ # Async बिल्डिंग ब्लॉक थोड़ा अधिक जटिल है क्योंकि इसमें रद्दीकरण और अपवाद निरंतरता भी शामिल है लेकिन अनिवार्य रूप से यह है।

एफ # वर्कफ़्लो वाक्यविन्यास का समर्थन करने के लिए हमें एक गणना अभिव्यक्ति (https://msdn.microsoft.com/en-us/library/dd233182.aspx) को परिभाषित करने की आवश्यकता है। हालांकि यह एक उन्नत एफ # सुविधा है, यह एफ # की सबसे आश्चर्यजनक विशेषताओं में से एक है। परिभाषित करने के लिए दो सबसे महत्वपूर्ण संचालन return & bind जिनका उपयोग DIY<_> बिल्डिंग ब्लॉक को DIY<_> बिल्डिंग ब्लॉक में जोड़ने के लिए F # द्वारा किया जाता है।

adaptTaskTask<'T> को DIY<'T> में अनुकूलित करने के लिए उपयोग किया जाता है। startChild कई simulatenous DIY<'T> शुरू करने की अनुमति देता है, ध्यान दें कि ऐसा करने के लिए यह नए धागे शुरू नहीं करता है लेकिन कॉलिंग थ्रेड का पुन: उपयोग करता है।

Starting workflow 
Waiting for key 
Result is: (706L, 1, 2, 3) 

कार्यक्रम ध्यान दें कि Waiting for key के रूप में तुरंत छपा है चलाते समय:

open System 
open System.Diagnostics 
open System.Threading 
open System.Threading.Tasks 

// Our Do It Yourself Async workflow is a function accepting a continuation ('T->unit). 
// The continuation is called when the result of the workflow is ready. 
// This may happen immediately or after awhile, the important thing is that 
// we don't block the calling thread which may then continue executing useful code. 
type DIY<'T> = ('T->unit)->unit 

// In order to support let!, do! and so on we implement a computation expression. 
// The two most important operations are returnValue/bind but delay is also generally 
// good to implement. 
module DIY = 

    // returnValue is called when devs uses return x in a workflow. 
    // returnValue passed v immediately to the continuation. 
    let returnValue (v : 'T) : DIY<'T> = 
     fun a -> 
      a v 

    // bind is called when devs uses let!/do! x in a workflow 
    // bind binds two DIY workflows together 
    let bind (t : DIY<'T>) (fu : 'T->DIY<'U>) : DIY<'U> = 
     fun a -> 
      let aa tv = 
       let u = fu tv 
       u a 
      t aa 

    let delay (ft : unit->DIY<'T>) : DIY<'T> = 
     fun a -> 
      let t = ft() 
      t a 

    // starts a DIY workflow as a subflow 
    // The way it works is that the workflow is executed 
    // which may be a delayed operation. But startChild 
    // should always complete immediately so in order to 
    // have something to return it returns a DIY workflow 
    // postProcess checks if the child has computed a value 
    // ie rv has some value and if we have computation ready 
    // to receive the value (rca has some value). 
    // If this is true invoke ca with v 
    let startChild (t : DIY<'T>) : DIY<DIY<'T>> = 
     fun a -> 
      let l = obj() 
      let rv = ref None 
      let rca = ref None 

      let postProcess() = 
       match !rv, !rca with 
       | Some v, Some ca -> 
        ca v 
        rv := None 
        rca := None 
       | _ , _ ->() 

      let receiver v = 
       lock l <| fun() -> 
        rv := Some v 
        postProcess() 

      t receiver 

      let child : DIY<'T> = 
       fun ca -> 
        lock l <| fun() -> 
         rca := Some ca 
         postProcess() 

      a child 

    let runWithContinuation (t : DIY<'T>) (f : 'T -> unit) : unit = 
     t f 

    // Adapts a task as a DIY workflow 
    let adaptTask (t : Task<'T>) : DIY<'T> = 
     fun a -> 
      let action = Action<Task<'T>> (fun t -> a t.Result) 
      ignore <| t.ContinueWith action 

    // Because C# generics doesn't allow Task<void> we need to have 
    // a special overload of for the unit Task. 
    let adaptUnitTask (t : Task) : DIY<unit> = 
     fun a -> 
      let action = Action<Task> (fun t -> a()) 
      ignore <| t.ContinueWith action 

    type DIYBuilder() = 
     member x.Return(v) = returnValue v 
     member x.Bind(t,fu) = bind t fu 
     member x.Delay(ft) = delay ft 

let diy = DIY.DIYBuilder() 

open DIY 

[<EntryPoint>] 
let main argv = 

    let delay (ms : int) = adaptUnitTask <| Task.Delay ms 

    let delayedValue ms v = 
     diy { 
      do! delay ms 
      return v 
     } 

    let complete = 
     diy { 
      let sw = Stopwatch() 
      sw.Start() 

      // Since we are executing these tasks concurrently 
      // the time this takes should be roughly 700ms 
      let! cd1 = startChild <| delayedValue 100 1 
      let! cd2 = startChild <| delayedValue 300 2 
      let! cd3 = startChild <| delayedValue 700 3 

      let! d1 = cd1 
      let! d2 = cd2 
      let! d3 = cd3 

      sw.Stop() 

      return sw.ElapsedMilliseconds,d1,d2,d3 
     } 

    printfn "Starting workflow" 

    runWithContinuation complete (printfn "Result is: %A") 

    printfn "Waiting for key" 

    ignore <| Console.ReadKey() 

    0 

कार्यक्रम के उत्पादन में कुछ इस तरह होना चाहिए:

किसी भी आगे की हलचल के बिना यहाँ नमूना कार्यक्रम है वर्कफ़्लो प्रारंभ करने से कंसोल थ्रेड अवरुद्ध नहीं है। लगभग 700ms के बाद परिणाम मुद्रित किया जाता है।

मुझे आशा है कि यह कुछ एफ # devs

+1

बहुत रोचक, धन्यवाद! –

2

अन्य उत्तर में काफी विस्तार से बहुत सारे के लिए दिलचस्प था, लेकिन जैसा कि मैंने शुरुआत मैं सी # और एफ # के बीच मतभेद से ऊपर फिसल गया।

एफ # एसिंक ब्लॉक नुस्खा कोड कैसे चलाना चाहिए, वास्तव में इसे चलाने के लिए वास्तव में एक निर्देश नहीं है।

आप अपनी नुस्खा का निर्माण करते हैं, शायद अन्य व्यंजनों (जैसे Async.Parallel) के साथ संयोजन। केवल तभी आप इसे चलाने के लिए सिस्टम से पूछते हैं, और आप इसे वर्तमान थ्रेड (उदा। Async.StartImmediate) पर या किसी नए कार्य, या कई अन्य तरीकों से कर सकते हैं।

तो यह एक decoupling है कि आप क्या करना चाहते हैं से इसे करना चाहिए।

सी # मॉडल को अक्सर 'हॉट टास्क' कहा जाता है क्योंकि आपके परिभाषा के हिस्से के रूप में कार्य आपके लिए शुरू किया जाता है, बनाम एफ # 'शीत कार्य' मॉडल।

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