एंटिटी फ्रेमवर्क Queryable async


97

मैं एंटिटी फ्रेमवर्क 6 का उपयोग करते हुए कुछ वेब एपीआई सामान पर काम कर रहा हूं और मेरी नियंत्रक विधियों में से एक "गेट ऑल" है जो मेरे डेटाबेस से तालिका की सामग्री प्राप्त करने की अपेक्षा करता है IQueryable<Entity>। मेरे भंडार में मैं सोच रहा हूं कि क्या यह अतुल्यकालिक रूप से करने के लिए कोई लाभप्रद कारण है क्योंकि मैं एस्बेस्ट के साथ ईएफ का उपयोग करने के लिए नया हूं।

मूल रूप से यह करने के लिए फोड़ा

 public async Task<IQueryable<URL>> GetAllUrlsAsync()
 {
    var urls = await context.Urls.ToListAsync();
    return urls.AsQueryable();
 }

बनाम

 public IQueryable<URL> GetAllUrls()
 {
    return context.Urls.AsQueryable();
 }

क्या async संस्करण वास्तव में यहाँ प्रदर्शन लाभ प्राप्त करेगा या क्या मैं पहली सूची (async मन का उपयोग करके) और IQueryable में जाने का अनुमान लगाकर अनावश्यक ओवरहेड प्राप्त कर रहा हूँ?


1
संदर्भ। Urls DbSet <URL> प्रकार का होता है, जो IQueryable <URL> को लागू करता है। इसलिए .Asable () निरर्थक है। msdn.microsoft.com/en-us/library/gg696460(v=vs.113).aspx मान लें कि आपने EF द्वारा प्रदान किए गए पैटर्न का अनुसरण किया है, या टूलिंग का उपयोग किया है जो आपके लिए संदर्भ बनाता है।
सीन बी

जवाबों:


223

समस्या यह प्रतीत होती है कि आपको गलतफहमी हो गई है कि एंटिटी फ्रेमवर्क के साथ कैसे काम करता है।

एंटिटी फ्रेमवर्क के बारे में

तो, आइए इस कोड को देखें:

public IQueryable<URL> GetAllUrls()
{
    return context.Urls.AsQueryable();
}

और इसके उपयोग का उदाहरण:

repo.GetAllUrls().Where(u => <condition>).Take(10).ToList()

वहां क्या होता है?

  1. हम IQueryableऑब्जेक्ट (अभी तक डेटाबेस तक नहीं पहुंच) का उपयोग कर रहे हैंrepo.GetAllUrls()
  2. हम IQueryableनिर्दिष्ट स्थिति का उपयोग करके एक नई वस्तु बनाते हैं.Where(u => <condition>
  3. हम IQueryableउपयोग करके निर्दिष्ट पेजिंग सीमा के साथ एक नई वस्तु बनाते हैं.Take(10)
  4. हम डेटाबेस का उपयोग करके परिणाम प्राप्त करते हैं .ToList()। हमारी IQueryableवस्तु sql (जैसे select top 10 * from Urls where <condition>) संकलित है । और डेटाबेस अनुक्रमित का उपयोग कर सकता है, sql सर्वर आपको आपके डेटाबेस से केवल 10 ऑब्जेक्ट भेजता है (डेटाबेस में संग्रहीत सभी अरब यूआरएल नहीं)

ठीक है, पहले कोड को देखें:

public async Task<IQueryable<URL>> GetAllUrlsAsync()
{
    var urls = await context.Urls.ToListAsync();
    return urls.AsQueryable();
}

उपयोग के समान उदाहरण के साथ हमें मिला:

  1. हम आपके डेटाबेस में उपयोग किए गए सभी अरब url को मेमोरी में लोड कर रहे हैं await context.Urls.ToListAsync();
  2. हमें मेमोरी ओवरफ्लो हो गई। अपने सर्वर को मारने का सही तरीका

Async / प्रतीक्षा के बारे में

क्यों async / प्रतीक्षा का उपयोग करने के लिए पसंद किया जाता है? आइए इस कोड को देखें:

var stuff1 = repo.GetStuff1ForUser(userId);
var stuff2 = repo.GetStuff2ForUser(userId);
return View(new Model(stuff1, stuff2));

यहाँ क्या हुआ?

  1. लाइन 1 पर शुरू var stuff1 = ...
  2. हम sql सर्वर को अनुरोध भेजते हैं कि हम कुछ सामान प्राप्त करना चाहते हैं userId
  3. हम प्रतीक्षा करते हैं (वर्तमान थ्रेड अवरुद्ध है)
  4. हम प्रतीक्षा करते हैं (वर्तमान थ्रेड अवरुद्ध है)
  5. .....
  6. Sql सर्वर हमें प्रतिक्रिया भेजें
  7. हम पंक्ति 2 में जाते हैं var stuff2 = ...
  8. हम sql सर्वर को अनुरोध भेजते हैं कि हम कुछ सामान प्राप्त करना चाहते हैं userId
  9. हम प्रतीक्षा करते हैं (वर्तमान थ्रेड अवरुद्ध है)
  10. और फिर
  11. .....
  12. Sql सर्वर हमें प्रतिक्रिया भेजें
  13. हम दृश्य प्रस्तुत करते हैं

तो चलिए इसके एक async संस्करण को देखते हैं:

var stuff1Task = repo.GetStuff1ForUserAsync(userId);
var stuff2Task = repo.GetStuff2ForUserAsync(userId);
await Task.WhenAll(stuff1Task, stuff2Task);
return View(new Model(stuff1Task.Result, stuff2Task.Result));

यहाँ क्या हुआ?

  1. हम sql सर्वर को stuff1 प्राप्त करने के लिए अनुरोध भेजते हैं (पंक्ति 1)
  2. हम sql सर्वर को stuff2 प्राप्त करने के लिए अनुरोध भेजते हैं (पंक्ति 2)
  3. हम sql सर्वर से प्रतिक्रियाओं की प्रतीक्षा करते हैं, लेकिन वर्तमान थ्रेड अवरुद्ध नहीं है, वह अन्य उपयोगकर्ताओं के प्रश्नों को संभाल सकता है
  4. हम दृश्य प्रस्तुत करते हैं

इसे करने का सही तरीका

तो यहाँ अच्छा कोड:

using System.Data.Entity;

public IQueryable<URL> GetAllUrls()
{
   return context.Urls.AsQueryable();
}

public async Task<List<URL>> GetAllUrlsByUser(int userId) {
   return await GetAllUrls().Where(u => u.User.Id == userId).ToListAsync();
}

ध्यान दें, IQueryable के लिए using System.Data.Entityविधि का उपयोग करने के ToListAsync()लिए आपको जोड़ना चाहिए ।

ध्यान दें, यदि आपको फ़िल्टरिंग और पेजिंग और सामान की आवश्यकता नहीं है, तो आपको काम करने की आवश्यकता नहीं है IQueryable। आप केवल await context.Urls.ToListAsync()भौतिकता के साथ उपयोग और काम कर सकते हैं List<Url>


3
चित्र देख @Korijn i2.iis.net/media/7188126/... से आईआईएस वास्तुकला का परिचय मैं कह सकता हूँ कि IIS में सभी अनुरोधों अतुल्यकालिक तरह से कार्रवाई की जाती है
विक्टर Lova

7
चूँकि आप GetAllUrlsByUserविधि में निर्धारित परिणाम पर कार्य नहीं कर रहे हैं , इसलिए आपको इसे बनाने की आवश्यकता नहीं है। बस टास्क वापस करें और अपने स्वयं के एक अनावश्यक राज्य मशीन को संकलक द्वारा उत्पन्न होने से बचाएं।
जॉनथॉन सुलिंगर

1
@JohnathonSullinger हालांकि जो खुशहाल प्रवाह में काम करेगा, क्या इसका कोई साइड-इफ़ेक्ट नहीं है कि यहाँ कोई अपवाद नहीं होगा और पहली जगह पर प्रचार होगा जिसका इंतजार है? (ऐसा नहीं है कि यह वास्तव में बुरा है, लेकिन यह व्यवहार में बदलाव है!)
हेनरी

9
दिलचस्प है कि कोई भी नोटिस नहीं करता है कि "async / प्रतीक्षा" में दूसरा कोड उदाहरण कुल गैर-अर्थ है, क्योंकि यह न तो EF और न ही EF कोर के थ्रेड को सुरक्षित रखता है, इसलिए समानांतर में चलने की कोशिश सिर्फ एक अपवाद को फेंक देगी।
त्सेंग

1
यद्यपि यह उत्तर सही है, मैं उपयोग करने से बचने की सलाह दूंगा asyncऔर awaitयदि आप सूची में कुछ भी नहीं कर रहे हैं। कॉलर को awaitइसे करने दें। जब आप इस स्तर पर कॉल का इंतजार करते return await GetAllUrls().Where(u => u.User.Id == userId).ToListAsync();हैं तो आप असेंबली का एक अतिरिक्त आवरण बना रहे होते हैं जब आप असेंबली को विघटित करते हैं और आईएल को देखते हैं।
अली खकपुरी

10

आपके द्वारा पोस्ट किए गए उदाहरण में एक बड़ा अंतर है, पहला संस्करण:

var urls = await context.Urls.ToListAsync();

यह बुरा है , यह मूल रूप से करता है select * from table, सभी परिणामों को स्मृति में लौटाता है और फिर डेटाबेस के खिलाफ whereकरने के बजाय स्मृति संग्रह में इसके खिलाफ लागू होता है select * from table where...

दूसरी विधि वास्तव में डेटाबेस को तब तक हिट नहीं करेगी जब तक कि एक क्वेरी लागू नहीं होती है IQueryable(शायद एक लाइनक .Where().Select()स्टाइल ऑपरेशन के माध्यम से जो केवल क्वेरी से मेल खाने वाले डीबी मानों को वापस कर देगा।

यदि आपके उदाहरण तुलनीय थे, तो asyncसंस्करण आमतौर पर प्रति अनुरोध थोड़ा धीमा होगा क्योंकि राज्य मशीन में अधिक ओवरहेड है जो संकलक asyncकार्यक्षमता की अनुमति देने के लिए उत्पन्न करता है।

हालांकि प्रमुख अंतर (और लाभ) यह है कि asyncसंस्करण अधिक समवर्ती अनुरोधों की अनुमति देता है क्योंकि यह प्रसंस्करण थ्रेड को ब्लॉक नहीं करता है, जबकि यह आईओ के पूरा होने की प्रतीक्षा कर रहा है (डीबी क्वेरी, फ़ाइल एक्सेस, वेब अनुरोध आदि)।


7
जब तक कोई क्वेरी IQueryable पर लागू नहीं होती है .... न तो IQueryable.here और IQueryable.Select क्वेरी को निष्पादित करने के लिए बाध्य करते हैं। पूर्व एक विधेय लागू करता है और बाद वाला एक प्रक्षेपण लागू करता है। यह तब तक निष्पादित नहीं किया जाता है जब तक कि कोई भौतिक ऑपरेटर का उपयोग नहीं किया जाता है, जैसे कि ToList, ToArray, सिंगल या फ़र्स्ट।
JJS

0

लंबी कहानी छोटी,
IQueryableआरयूएन प्रक्रिया को स्थगित करने और सबसे पहले अभिव्यक्ति को अन्य IQueryableअभिव्यक्तियों के साथ बनाने के लिए डिज़ाइन किया गया है , और फिर अभिव्यक्ति की व्याख्या करता है और समग्र रूप से चलाता है।
लेकिन ToList()विधि (या उस तरह के कुछ प्रकार), अभिव्यक्ति को "जैसा है" तुरंत चलाने के लिए मानसिक हैं।
आपकी पहली विधि ( GetAllUrlsAsync), imediately चलेगी, क्योंकि यह विधि IQueryableद्वारा पीछा किया जाता ToListAsync()है। इसलिए यह तुरंत (एसिंक्रोनस) चलता है, और IEnumerableएस का एक गुच्छा लौटाता है ।
इस बीच आपकी दूसरी विधि ( GetAllUrls), नहीं चलेगी। इसके बजाय, यह एक अभिव्यक्ति देता है और अभिव्यक्ति को चलाने के लिए इस पद्धति का CALLER जिम्मेदार है।

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