2016-05-03 8 views
7

के भीतर सिंक्रोनस I/O मान लें कि मेरे पास एक विंडोज सेवा है जो कुछ काम कर रही है, फिर थोड़ी देर के लिए सो रही है, हमेशा के लिए और अधिक (जब तक सेवा बंद नहीं होती नीचे)।एक एसिंक/प्रतीक्षा-आधारित विंडोज सेवा

private void WorkerThreadFunc() 
{ 
    while (!shuttingDown) 
    { 
     DoSomething(); 
     Thread.Sleep(10); 
    } 
} 

और सेवा के OnStop में, मैं किसी भी तरह कि ShuttingDown ध्वज सेट और फिर धागा शामिल हो: तो सेवा के onStart में, मैं एक धागा जिसका प्रवेश बिंदु है की तरह कुछ शुरू कर सकता है। असल में ऐसे कई धागे और अन्य धागे भी हो सकते हैं, सभी ऑनस्टार्ट में शुरू हो गए और ऑनस्टॉप में बंद/शामिल हो गए।

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

private async Task WorkAsync(CancellationToken cancel) 
{ 
    while (true) 
    { 
     cancel.ThrowIfCancellationRequested(); 
     DoSomething(); 
     await Task.Delay(10, cancel).ConfigureAwait(false); 
    } 
} 

प्रश्न # 1: अगर मैं सही ढंग से समझ, "WorkerThreadFunc" ऊपर दिखाए गए के बराबर की तरह कुछ हो सकता है उह ... है ना? मैं async/प्रतीक्षा करने के लिए नया हूं और अभी भी इसके चारों ओर अपना सिर लेने की कोशिश कर रहा हूं।

मान लीजिए कि यह सही है, अब मान लें कि DoSomething() कॉल हार्डवेयर के कुछ हिस्सों में एक तुल्यकालिक लेखन I/O है (या शामिल है)। अगर मैं सही ढंग से समझ रहा हूं:

प्रश्न # 2: यह बुरा है? मुझे एक async/प्रतीक्षा-आधारित प्रोग्राम में एक कार्य के भीतर तुल्यकालिक I/O नहीं करना चाहिए? चूंकि यह थ्रेड पूल से धागे से जुड़ा हुआ है, जबकि I/O हो रहा है, और थ्रेड पूल से धागे अत्यधिक सीमित संसाधन हैं? कृपया ध्यान दें कि मेरे पास दर्जनों ऐसे श्रमिक हार्डवेयर के विभिन्न टुकड़ों के साथ-साथ जा रहे हैं।

मुझे यकीन नहीं है कि मैं इसे सही ढंग से समझ रहा हूं - मुझे यह विचार मिल रहा है कि स्टीफन क्लेरी के "Task.Run Etiquette Examples: Don't Use Task.Run for the Wrong Thing" जैसे लेखों से यह बुरा है, लेकिन यह विशेष रूप से टास्क के अंदर काम को अवरुद्ध करने में बुरा होने के बारे में है। मुझे यकीन नहीं है कि अगर मैं इसे सीधे कर रहा हूं, तो यह भी बुरा है, जैसा ऊपर "निजी एसिंक कार्य कार्य()" उदाहरण में है?

यह मानते हुए कि बहुत बुरा है, तो अगर मैं सही ढंग से समझ मैं बजाय nonblocking (इसके बारे में एक nonblocking संस्करण बनाने अगर यह पहले से ही मौजूद नहीं है) DoSomething के संस्करण का उपयोग करना चाहिए, और उसके बाद:

private async Task WorkAsync(CancellationToken cancel) 
{ 
    while (true) 
    { 
     cancel.ThrowIfCancellationRequested(); 
     await DoSomethingAsync(cancel).ConfigureAwait(false); 
     await Task.Delay(10, cancel).ConfigureAwait(false); 
    } 
} 

प्रश्न # 3: लेकिन ... क्या होगा यदि किसी तीसरे पक्ष की लाइब्रेरी से कुछ करना है, जिसे मुझे उपयोग करना चाहिए और बदल नहीं सकता है, और वह लाइब्रेरी DoSomething के एक गैर-ब्लॉकिंग संस्करण का पर्दाफाश नहीं करती है? यह पत्थर में बस एक काला बॉक्स सेट है जो किसी बिंदु पर हार्डवेयर के एक टुकड़े को अवरुद्ध करता है।

शायद मैं इसे लपेटूं और कार्यसंचार स्रोत का उपयोग करें? कुछ ऐसा:

private async Task WorkAsync(CancellationToken cancel) 
{ 
    while (true) 
    { 
     cancel.ThrowIfCancellationRequested(); 
     await WrappedDoSomething().ConfigureAwait(false); 
     await Task.Delay(10, cancel).ConfigureAwait(false); 
    } 
} 

private Task WrappedDoSomething() 
{ 
    var tcs = new TaskCompletionSource<object>(); 
    DoSomething(); 
    tcs.SetResult(null); 
    return tcs.Task; 
} 

लेकिन ऐसा लगता है कि यह इस मुद्दे को हल करने के बजाए थोड़ा और आगे दबा रहा है। WorkAsync() अभी भी अवरुद्ध होगा जब इसे लपेटा गया कुछ(), और केवल इसके लिए "प्रतीक्षा" प्राप्त करें, इसके बाद लपेटा हुआ कुछ() पहले ही ब्लॉकिंग कार्य पूरा कर चुका है। सही?

सामान्य मामले में (यदि मैं सही ढंग से समझता हूं) को देखते हुए एसिंक/प्रतीक्षा को प्रोग्राम में ऊपर और नीचे "फैलाने" की अनुमति दी जानी चाहिए, तो इसका मतलब यह होगा कि अगर मुझे ऐसी लाइब्रेरी का उपयोग करने की ज़रूरत है, तो मैं इसका मतलब दूंगा अनिवार्य रूप से कार्यक्रम async/प्रतीक्षा-आधारित नहीं करना चाहिए? मुझे थ्रेड/वर्कर थ्रेडफनक/थ्रेड पर वापस जाना चाहिए। नींद की दुनिया?

क्या होगा यदि एक async/await- आधारित प्रोग्राम पहले से मौजूद है, अन्य चीजें कर रहा है, लेकिन अब ऐसी लाइब्रेरी का उपयोग करने वाली अतिरिक्त कार्यक्षमता को इसमें जोड़ा जाना चाहिए? क्या इसका मतलब यह है कि एसिंक/प्रतीक्षा-आधारित कार्यक्रम को थ्रेड/इत्यादि-आधारित प्रोग्राम के रूप में फिर से लिखा जाना चाहिए?

उत्तर

9

असल में वहाँ कई तरह के धागे, और अन्य सूत्र भी हो सकता है, सब शुरू कर दिया ऑनस्टार्ट में और बंद/ऑनस्टॉप में शामिल हो गए।

एक तरफ ध्यान दें, आमतौर पर एक "मास्टर" थ्रेड होना आसान होता है जो अन्य सभी को शुरू/जोड़ देगा। फिर OnStart/OnStop बस मास्टर थ्रेड से संबंधित है।

अगर मैं बजाय एक async में बात की इस तरह से करना चाहता हूँ/इंतजार आधारित Windows सेवा है, यह मैं onStart उन पर रद्द करने योग्य कार्य बनाने लेकिन इंतजार है (या प्रतीक्षा) नहीं हो सकता था की तरह है, और उन को रद्द OnStop है लगता है कार्य और फिर कार्य। जब सभी()। उन पर प्रतीक्षा करें()।

यह एक पूरी तरह स्वीकार्य दृष्टिकोण है। शायद CancellationToken नीचे पास करना चाहते हैं

;:

अगर मैं सही ढंग से समझ, "WorkerThreadFunc" ऊपर दिखाए गए के बराबर की तरह कुछ हो सकता है रद्द भी, तुल्यकालिक कोड के द्वारा प्रयोग किया जा सकता है:

private async Task WorkAsync(CancellationToken cancel) 
{ 
    while (true) 
    { 
    DoSomething(cancel); 
    await Task.Delay(10, cancel).ConfigureAwait(false); 
    } 
} 

प्रश्न # 1: उह ... है ना? मैं async/प्रतीक्षा करने के लिए नया हूं और अभी भी इसके चारों ओर अपना सिर लेने की कोशिश कर रहा हूं।

यह गलत नहीं है, लेकिन यह केवल आप एक Win32 सेवा है, जो आप के लिए ज्यादा कुछ नहीं है पर एक धागा बचाता है।

प्रश्न # 2: यह बुरा है? मुझे एक async/प्रतीक्षा-आधारित प्रोग्राम में एक कार्य के भीतर तुल्यकालिक I/O नहीं करना चाहिए? चूंकि यह थ्रेड पूल से धागे से जुड़ा हुआ है, जबकि I/O हो रहा है, और थ्रेड पूल से धागे अत्यधिक सीमित संसाधन हैं? कृपया ध्यान दें कि मेरे पास दर्जनों ऐसे श्रमिक हार्डवेयर के विभिन्न टुकड़ों के साथ-साथ जा रहे हैं।

धागे के दर्जन बहुत नहीं हैं। आम तौर पर, एसिंक्रोनस I/O बेहतर होता है क्योंकि यह किसी भी थ्रेड का उपयोग नहीं करता है, लेकिन इस मामले में आप डेस्कटॉप पर हैं, इसलिए धागे अत्यधिक सीमित संसाधन नहीं हैं। async यूआई ऐप्स पर सबसे अधिक फायदेमंद है (जहां यूआई थ्रेड विशेष है और उसे मुक्त करने की आवश्यकता है), और एएसपी.NET ऐप्स जिन्हें स्केल करने की आवश्यकता है (जहां थ्रेड पूल स्केलेबिलिटी को सीमित करता है)।

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

मैं विचार है कि यह स्टीफन Cleary के "Task.Run शिष्टाचार उदाहरण: गलत बात के लिए Task.Run का प्रयोग न करें" की तरह लेख से बुरा है हो रही है, लेकिन वह यह काम अवरुद्ध करने के लिए बुरा होने के बारे में विशेष रूप से है कार्य के भीतर। रुन।

हां, यह विशेष रूप से Task.Run का उपयोग कर तुल्यकालिक तरीकों लपेट और नाटक वे अतुल्यकालिक कर रहे हैं के बारे में है। यह एक आम गलती है; यह सब एक दूसरे के लिए एक धागा पूल धागा व्यापार है।

यह मानते हुए कि बहुत बुरा है, तो अगर मैं सही ढंग से समझ मैं बजाय DoSomething की nonblocking संस्करण (इसके बारे में एक nonblocking संस्करण बनाने अगर यह पहले से ही मौजूद नहीं है) का उपयोग करना चाहिए

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

प्रश्न # 3: लेकिन ...क्या होगा यदि किसी तीसरे पक्ष की लाइब्रेरी से कुछ करना है, जिसे मुझे उपयोग करना चाहिए और बदल नहीं सकता है, और वह लाइब्रेरी DoSomething के एक गैर-ब्लॉकिंग संस्करण का पर्दाफाश नहीं करती है? यह पत्थर में बस एक काला बॉक्स सेट है जो किसी बिंदु पर हार्डवेयर के एक टुकड़े को अवरुद्ध करता है।

फिर बस इसे सीधे कॉल करें।

शायद मैं इसे लपेटूं और कार्यसंचार स्रोत का उपयोग करें?

नहीं, यह कुछ भी उपयोगी नहीं करता है। यह सिर्फ इसे तुल्यकालिक रूप से कॉल करता है और फिर पहले से ही पूरा कार्य लौटाता है।

लेकिन ऐसा लगता है कि यह इस मुद्दे को हल करने के बजाए थोड़ा और आगे दबा रहा है। WorkAsync() अभी भी अवरुद्ध होगा जब इसे लपेटा गया कुछ(), और केवल इसके लिए "प्रतीक्षा" प्राप्त करें, इसके बाद लपेटा हुआ कुछ() पहले ही ब्लॉकिंग कार्य पूरा कर चुका है। सही?

यूप।

यह देखते हुए कि सामान्य स्थिति में (अगर मैं सही ढंग से समझ) async/इंतजार इसका मतलब यह होगा सभी तरह ऊपर और नीचे "फैल" करने के लिए एक कार्यक्रम में अनुमति दी जानी चाहिए, कि मैं इस तरह के एक पुस्तकालय का उपयोग करने की आवश्यकता है , मुझे अनिवार्य रूप से प्रोग्राम async/प्रतीक्षा-आधारित नहीं बनाना चाहिए? मुझे थ्रेड/वर्कर थ्रेडफनक/थ्रेड पर वापस जाना चाहिए। नींद की दुनिया?

मान लीजिए कि आपके पास पहले से ही एक अवरुद्ध Win32 सेवा है, तो शायद इसे ठीक रखने के लिए ठीक है। यदि आप एक नया लिख ​​रहे हैं, व्यक्तिगत रूप से मैं इसे async बना देता हूं ताकि थ्रेड को कम किया जा सके और एसिंक्रोनस एपीआई की अनुमति मिल सके, लेकिन में नहीं है, इसे किसी भी तरह से करने के लिए। मैं Task से अधिक Thread एस सामान्य रूप से पसंद करता हूं, क्योंकि Task एस (अपवाद सहित) से परिणाम प्राप्त करना बहुत आसान है।

"async सभी तरह से" नियम केवल एक ही रास्ता जाता है। यही कारण है, एक बार आप एक async विधि कॉल, तो इसकी कॉलर async होना चाहिए, और अपने फोन करने वाले async होना चाहिए, आदि यह नहीं मतलब है कि हर विधि एक async विधि द्वारा बुलाया async होना चाहिए।

तो, एसिंक Win32 सेवा होने का एक अच्छा कारण होगा यदि एक एसिंक-केवल एपीआई है जिसे आपको उपभोग करने की आवश्यकता है। इससे आपकी DoSomething विधि async DoSomethingAsync बनने का कारण बन जाएगी।

क्या होगा यदि एसिंक/प्रतीक्षा-आधारित प्रोग्राम पहले से मौजूद है, अन्य चीजें कर रहा है, लेकिन अब ऐसी लाइब्रेरी का उपयोग करने वाली अतिरिक्त कार्यक्षमता को इसमें जोड़ा जाना चाहिए? क्या इसका मतलब यह है कि एसिंक/प्रतीक्षा-आधारित कार्यक्रम को थ्रेड/इत्यादि-आधारित प्रोग्राम के रूप में फिर से लिखा जाना चाहिए?

नहीं। आप हमेशा async विधि से केवल ब्लॉक कर सकते हैं। उचित दस्तावेज़ीकरण के साथ, जब आप इस कोड को एक वर्ष से फिर से उपयोग/रखरखाव कर रहे हैं, तो आप अपने पिछले स्वयं की कसम खाता नहीं है। :)

+1

यह एक बहुत ही समझदार उत्तर है। कारण यह नहीं है कि सब कुछ एसिंक है या नहीं होना चाहिए कि async डेवलपर के लिए काम करता है और कोड की प्रति इकाई की बग की दर बढ़ाता है। यह मुख्य काउंटर प्वाइंट है। यदि आप एसिंक जाते हैं तो एक ठोस लाभ होना चाहिए। – usr

1

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

यदि DoSomething() तुल्यकालिक है, तो आप इसके बजाय Timer कक्षा पर स्विच कर सकते हैं। यह कई टाइमर को धागे की एक छोटी राशि का उपयोग करने की अनुमति देता है। ..

SemaphoreSlim _shutdownEvent = new SemaphoreSlim(0,1); 

public async Task Stop() 
{ 
    return await _shutdownEvent.WaitAsync(); 
} 

private void WorkerThreadFunc() 
{ 
    while (!shuttingDown) 
    { 
     DoSomething(); 
     Thread.Sleep(10); 
    } 
    _shutdownEvent.Release(); 
} 

जिसका अर्थ है कि शटडाउन के दौरान आप यह कर सकते हैं:

तो यह महत्वपूर्ण है कि नौकरियों को पूरा कर सकते, तो आप अपने कार्यकर्ता कक्षाएं इस तरह संशोधित कर सकते हैं

var tasks = myServices.Select(x=> x.Stop()); 
Task.WaitAll(tasks); 
1

एक धागा एक समय में केवल एक ही चीज कर सकते हैं। हालांकि यह आपके DoSomething पर काम कर रहा है, यह कुछ और नहीं कर सकता है।

एक साक्षात्कार में एरिक लिपर्ट ने async-await in a restaurant metaphor का वर्णन किया। वह केवल कार्यक्षमता के लिए async-await का उपयोग करने का सुझाव देता है जहां ऑपरेटर इनपुट के जवाब की तरह, प्रक्रिया समाप्त होने की प्रतीक्षा करने के बजाय आपका धागा अन्य चीजें कर सकता है।

हां, आपका धागा इंतजार नहीं कर रहा है, यह DoSomething में कड़ी मेहनत कर रहा है। और जब तक DoSomething इंतजार नहीं कर रहा है, तब तक आपका धागा अगली चीज़ करने के लिए कुछ नहीं करेगा।

तो यदि प्रक्रिया के दौरान आपके धागे के पास कुछ सार्थक है तो कुछ करना निष्पादित हो रहा है, एक और धागा कुछ करने के लिए बुद्धिमान है, जबकि आपका मूल धागा सार्थक सामान कर रहा है। कार्य। रुन (() => DoSomething()) यह आपके लिए कर सकता है। जब तक टास्क.रुन नामक धागा इस कार्य के लिए इंतजार नहीं करता है, तो यह अन्य चीजों को करने के लिए स्वतंत्र है।

आप भी अपनी प्रक्रिया को रद्द करना चाहते हैं। कुछ रद्द नहीं किया जा सकता है। तो अगर रद्दीकरण का अनुरोध किया जाता है तो आपको कुछ भी पूरा होने तक प्रतीक्षा करनी होगी।

नीचे एक प्रारंभ बटन और एक रद्द बटन के साथ एक रूप में अपनी DoSomething है। अपने धागा DoingSomething जाता है, सार्थक बातें अपने जीयूआई धागा करने के लिए चाहते हो सकता है में से एक रद्द बटन दबाने का जवाब है:

void CancellableDoSomething(CancellationToken token) 
{ 
    while (!token.IsCancellationRequested) 
    { 
     DoSomething() 
    } 
} 

async Task DoSomethingAsync(CancellationToken token) 
{ 
    var task = Task.Run(CancellableDoSomething(token), token); 
    // if you have something meaningful to do, do it now, otherwise: 
    return Task; 
} 

CancellationTokenSource cancellationTokenSource = null; 

private async void OnButtonStartSomething_Clicked(object sender, ...) 
{ 
    if (cancellationTokenSource != null) 
     // already doing something 
     return 

    // else: not doing something: start doing something 
    cancellationTokenSource = new CancellationtokenSource() 
    var task = AwaitDoSomethingAsync(cancellationTokenSource.Token); 
    // if you have something meaningful to do, do it now, otherwise: 
    await task; 
    cancellationTokenSource.Dispose(); 
    cancellationTokenSource = null; 
} 

private void OnButtonCancelSomething(object sender, ...) 
{ 
    if (cancellationTokenSource == null) 
     // not doing something, nothing to cancel 
     return; 

    // else: cancel doing something 
    cancellationTokenSource.Cancel(); 
}