C# Language
Async-का इंतजार
खोज…
परिचय
C # में, async
घोषित एक विधि एक तुल्यकालिक प्रक्रिया के भीतर ब्लॉक नहीं होगी, यदि आप I / O आधारित संचालन (जैसे वेब एक्सेस, फ़ाइलों के साथ काम करना ...) का उपयोग कर रहे हैं। इस तरह के async चिह्नित तरीकों में से परिणाम के उपयोग के माध्यम से प्रतीक्षा की जा सकती है await
कीवर्ड।
टिप्पणियों
एक async
विधि void
, Task
या Task<T>
लौटा सकती है।
वापसी प्रकार Task
समाप्त होने की विधि की प्रतीक्षा करेगा और परिणाम void
होगा। Task<T>
विधि पूर्ण होने के बाद टाइप T
से मान लौटाएगा।
async
तरीकों लौटना चाहिए Task
या Task<T>
, के रूप में करने का विरोध किया void
, लगभग सभी परिस्थितियों में। async void
विधियाँ ed का await
नहीं कर सकती हैं, जिससे कई तरह की समस्याएं होती हैं। एकमात्र परिदृश्य जहां एक async
को void
होना चाहिए, एक घटना हैंडलर के मामले में है।
अपने async
विधि को राज्य मशीन में बदलकर async
/ await
कार्य करता है। यह पर्दे के पीछे एक संरचना बनाकर करता है जो वर्तमान स्थिति और किसी भी संदर्भ (जैसे स्थानीय चर) को संग्रहीत करता है, और जब भी कोई प्रतीक्षित प्रतीक्षा पूरी होती है, तो राज्यों को अग्रिम करने के लिए (और किसी भी संबद्ध कोड को चलाने के लिए MoveNext()
एक MoveNext()
विधि को उजागर करता है।
सरल लगातार कॉल
public async Task<JobResult> GetDataFromWebAsync()
{
var nextJob = await _database.GetNextJobAsync();
var response = await _httpClient.GetAsync(nextJob.Uri);
var pageContents = await response.Content.ReadAsStringAsync();
return await _database.SaveJobResultAsync(pageContents);
}
यहां ध्यान देने वाली मुख्य बात यह है कि जब प्रत्येक await
पद्धति को अतुल्यकालिक रूप से कहा जाता है - और उस कॉल के लिए नियंत्रण को सिस्टम में वापस लाया जाता है - विधि के अंदर प्रवाह रैखिक होता है और इसके कारण किसी विशेष उपचार की आवश्यकता नहीं होती है asynchrony। यदि किसी भी विधि को विफल कहा जाता है, तो अपवाद को "अपेक्षित रूप से" संसाधित किया जाएगा, जिसका अर्थ है कि इस मामले में इसका मतलब है कि विधि निष्पादन समाप्त हो जाएगा और अपवाद ढेर हो जाएगा।
ट्राई / कैच / अंत में
C # 6.0 के रूप में, await
कीवर्ड को अब एक catch
और finally
ब्लॉक में उपयोग किया जा सकता है।
try {
var client = new AsyncClient();
await client.DoSomething();
} catch (MyException ex) {
await client.LogExceptionAsync();
throw;
} finally {
await client.CloseAsync();
}
C # 6.0 से पहले, आपको निम्नलिखित की तर्ज पर कुछ करने की आवश्यकता होगी। ध्यान दें कि 6.0 ने Null Propagating ऑपरेटर के साथ null चेक को भी साफ़ किया है।
AsynClient client;
MyException caughtException;
try {
client = new AsyncClient();
await client.DoSomething();
} catch (MyException ex) {
caughtException = ex;
}
if (client != null) {
if (caughtException != null) {
await client.LogExceptionAsync();
}
await client.CloseAsync();
if (caughtException != null) throw caughtException;
}
कृपया ध्यान दें कि यदि आप async
द्वारा निर्मित कार्य का इंतजार नहीं करते हैं (उदाहरण के लिए Task.Run
द्वारा बनाया गया कार्य), तो कुछ Task.Run
कार्य द्वारा फेंके गए अपवादों पर टूट सकते हैं, भले ही यह आस-पास की कोशिश / कैच द्वारा नियंत्रित किया गया हो। ऐसा इसलिए होता है क्योंकि डिबगर इसे उपयोगकर्ता कोड के संबंध में असंबद्ध मानता है। विजुअल स्टूडियो में, "जस्ट माय कोड" नामक एक विकल्प है, जिसे डिबगर को ऐसी स्थितियों में तोड़ने से रोकने के लिए अक्षम किया जा सकता है।
Web.config सेटअप सही async व्यवहार के लिए 4.5 को लक्षित करने के लिए।
Web.config system.web.httpRuntime को यह सुनिश्चित करने के लिए 4.5 लक्ष्य होना चाहिए कि थ्रेड आपके async विधि को फिर से शुरू करने से पहले अनुरोध संदर्भ को रेंट कर देगा।
<httpRuntime targetFramework="4.5" />
Async और इंतजार करने के लिए 4.5 से पहले ASP.NET पर अपरिभाषित व्यवहार है। Async / प्रतीक्षा एक अनियंत्रित थ्रेड पर फिर से शुरू होगी जिसमें अनुरोध संदर्भ नहीं हो सकता है। लोड के तहत आवेदन प्रतीक्षा के बाद HttpContext तक पहुंचने वाले अशक्त संदर्भ अपवादों के साथ बेतरतीब ढंग से विफल हो जाएंगे। HttpContext.Current का WebApi में उपयोग करना Async के कारण खतरनाक है
समवर्ती कॉल
यह संभव है कि पहले प्रतीक्षा योग्य कार्यों को लागू करके और फिर उनकी प्रतीक्षा करके कई कॉलों का समवर्ती रूप से इंतजार किया जाए।
public async Task RunConcurrentTasks()
{
var firstTask = DoSomethingAsync();
var secondTask = DoSomethingElseAsync();
await firstTask;
await secondTask;
}
वैकल्पिक रूप से, Task.WhenAll
को एक Task
में कई कार्यों को समूहीकृत करने के लिए इस्तेमाल किया जा सकता है, जो तब पूरा हो जाता है जब इसके सभी पास किए गए कार्य पूरे हो जाते हैं।
public async Task RunConcurrentTasks()
{
var firstTask = DoSomethingAsync();
var secondTask = DoSomethingElseAsync();
await Task.WhenAll(firstTask, secondTask);
}
आप इसे लूप के अंदर भी कर सकते हैं, उदाहरण के लिए:
List<Task> tasks = new List<Task>();
while (something) {
// do stuff
Task someAsyncTask = someAsyncMethod();
tasks.Add(someAsyncTask);
}
await Task.WhenAll(tasks);
Task.WhenAll के साथ कई कार्यों की प्रतीक्षा करने के बाद किसी कार्य से परिणाम प्राप्त करने के लिए, बस फिर से कार्य की प्रतीक्षा करें। चूंकि कार्य पहले ही पूरा हो चुका है, इसलिए यह परिणाम को वापस लौटा देगा
var task1 = SomeOpAsync();
var task2 = SomeOtherOpAsync();
await Task.WhenAll(task1, task2);
var result = await task2;
इसके अलावा, Task.WhenAny
कई कार्य समानांतर में, जैसे निष्पादित करने के लिए इस्तेमाल किया जा सकता Task.WhenAll
ऊपर, अंतर यह है कि इस विधि जब आपूर्ति कार्यों के किसी भी पूरा हो जाएगा पूरा हो जाएगा के साथ।
public async Task RunConcurrentTasksWhenAny()
{
var firstTask = TaskOperation("#firstTask executed");
var secondTask = TaskOperation("#secondTask executed");
var thirdTask = TaskOperation("#thirdTask executed");
await Task.WhenAny(firstTask, secondTask, thirdTask);
}
RunConcurrentTasksWhenAny
द्वारा लौटाया गया Task
तब पूरा होगा जब firstTask
, secondTask
या thirdTask
पूरा होता है।
प्रतीक्षा ऑपरेटर और async कीवर्ड
await
संचालक और async
कीवर्ड एक साथ आते हैं:
एसिंक्रोनस विधि जिसमें वेट का उपयोग किया जाता है उसे एसिंक्स कीवर्ड द्वारा संशोधित किया जाना चाहिए।
विपरीत हमेशा सच नहीं है: आप के रूप में एक विधि चिह्नित कर सकते हैं async
का उपयोग किए बिना await
अपने शरीर में।
प्रतीक्षित कार्य पूरा होने तक कोड के निष्पादन को निलंबित करने के लिए वास्तव में क्या await
; किसी भी कार्य की प्रतीक्षा की जा सकती है।
नोट: आप async विधि की प्रतीक्षा नहीं कर सकते हैं जो कुछ भी नहीं देता है (शून्य)।
दरअसल, 'सस्पेंड्स' शब्द थोड़ा भ्रामक है क्योंकि न केवल निष्पादन बंद हो जाता है, बल्कि अन्य कार्यों को निष्पादित करने के लिए धागा मुक्त हो सकता है। हुड के तहत, await
को कंपाइलर जादू के एक बिट द्वारा कार्यान्वित किया जाता है: यह एक विधि को दो भागों में विभाजित करता है - await
से पहले और बाद में। बाद वाले हिस्से को निष्पादित कार्य पूरा होने पर निष्पादित किया जाता है।
यदि हम कुछ महत्वपूर्ण विवरणों को अनदेखा करते हैं, तो कंपाइलर आपके लिए ऐसा करता है:
public async Task<TResult> DoIt()
{
// do something and acquire someTask of type Task<TSomeResult>
var awaitedResult = await someTask;
// ... do something more and produce result of type TResult
return result;
}
हो जाता है:
public Task<TResult> DoIt()
{
// ...
return someTask.ContinueWith(task => {
var result = ((Task<TSomeResult>)task).Result;
return DoIt_Continuation(result);
});
}
private TResult DoIt_Continuation(TSomeResult awaitedResult)
{
// ...
}
किसी भी सामान्य विधि को निम्न तरीके से async में बदला जा सकता है:
await Task.Run(() => YourSyncMethod());
यह तब फायदेमंद हो सकता है जब आपको यूआई को फ्रीज करने के बिना यूआई थ्रेड पर एक लंबी चलने वाली विधि को निष्पादित करने की आवश्यकता होती है।
लेकिन यहां एक बहुत महत्वपूर्ण टिप्पणी है: एसिंक्रोनस का मतलब हमेशा समवर्ती (समानांतर या यहां तक कि बहु-थ्रेडेड) नहीं होता है। यहां तक कि एक ही धागे पर, async
- await
अभी भी अतुल्यकालिक कोड के लिए अनुमति देता है। उदाहरण के लिए, यह कस्टम कार्य शेड्यूलर देखें । इस तरह के एक 'पागल' कार्य अनुसूचक बस कार्यों को कार्य में बदल सकता है जिसे संदेश लूप प्रसंस्करण के भीतर कहा जाता है।
हमें खुद से पूछने की आवश्यकता है: हमारी विधि DoIt_Continuation
की निरंतरता को कौन सा धागा निष्पादित करेगा?
डिफ़ॉल्ट रूप से await
ऑपरेटर वर्तमान सिंक्रनाइज़ेशन संदर्भ के साथ निरंतरता के निष्पादन को निर्धारित करता है । इसका मतलब है कि WinForms और WPF निरंतरता के लिए डिफ़ॉल्ट रूप से UI थ्रेड में चलता है। यदि, किसी कारण से, आपको इस व्यवहार को बदलने की आवश्यकता है, तो विधि का उपयोग करें। Task.ConfigureAwait()
:
await Task.Run(() => YourSyncMethod()).ConfigureAwait(continueOnCapturedContext: false);
बिना इंतजार किए टास्क लौटा देना
अतुल्यकालिक संचालन करने वाले तरीकों का await
करने की आवश्यकता नहीं है अगर:
- विधि के अंदर केवल एक अतुल्यकालिक कॉल है
- अतुल्यकालिक कॉल विधि के अंत में है
- टास्क के भीतर होने वाले अपवाद को पकड़ना / संभालना आवश्यक नहीं है
इस पद्धति पर विचार करें जो Task
लौटाता है:
public async Task<User> GetUserAsync(int id)
{
var lookupKey = "Users" + id;
return await dataStore.GetByKeyAsync(lookupKey);
}
यदि GetByKeyAsync
पास GetUserAsync
(किसी Task<User>
वापस करने वाला Task<User>
) के समान हस्ताक्षर है, तो विधि को सरल बनाया जा सकता है:
public Task<User> GetUserAsync(int id)
{
var lookupKey = "Users" + id;
return dataStore.GetByKeyAsync(lookupKey);
}
इस मामले में, विधि चिह्नित किए जाने की जरूरत नहीं है async
, भले ही यह एक अतुल्यकालिक आपरेशन preforming है। GetByKeyAsync
द्वारा लौटाए गए टास्क को सीधे कॉलिंग मेथड में पास किया जाता है, जहाँ यह एड का await
करेगा।
महत्वपूर्ण : Task
वापस करने के लिए इसे इंतजार करने के बजाय, विधि के अपवाद व्यवहार को बदल देता है, क्योंकि यह कार्य को शुरू करने वाली विधि के अंदर अपवाद को नहीं फेंकेगा लेकिन उस विधि में जो इसे इंतजार कर रहा है।
public Task SaveAsync()
{
try {
return dataStore.SaveChangesAsync();
}
catch(Exception ex)
{
// this will never be called
logger.LogException(ex);
}
}
// Some other code calling SaveAsync()
// If exception happens, it will be thrown here, not inside SaveAsync()
await SaveAsync();
यह प्रदर्शन में सुधार करेगा क्योंकि यह कंपाइलर को अतिरिक्त एसिंक्स स्टेट मशीन की पीढ़ी को बचाएगा।
Async कोड पर ब्लॉक करने से गतिरोध पैदा हो सकता है
यह एसिंक्स कॉल पर ब्लॉक करने के लिए एक बुरा अभ्यास है क्योंकि यह उन वातावरणों में गतिरोध पैदा कर सकता है जिनमें एक सिंक्रनाइज़ेशन संदर्भ होता है। सबसे अच्छा अभ्यास async / वेट का उपयोग करना है "सभी तरह से नीचे।" उदाहरण के लिए, निम्न Windows प्रपत्र कोड एक गतिरोध का कारण बनता है:
private async Task<bool> TryThis()
{
Trace.TraceInformation("Starting TryThis");
await Task.Run(() =>
{
Trace.TraceInformation("In TryThis task");
for (int i = 0; i < 100; i++)
{
// This runs successfully - the loop runs to completion
Trace.TraceInformation("For loop " + i);
System.Threading.Thread.Sleep(10);
}
});
// This never happens due to the deadlock
Trace.TraceInformation("About to return");
return true;
}
// Button click event handler
private void button1_Click(object sender, EventArgs e)
{
// .Result causes this to block on the asynchronous call
bool result = TryThis().Result;
// Never actually gets here
Trace.TraceInformation("Done with result");
}
अनिवार्य रूप से, एक बार async कॉल पूरा होने के बाद, यह सिंक्रनाइज़ेशन संदर्भ के उपलब्ध होने की प्रतीक्षा करता है। हालाँकि, इवेंट हैंडलर सिंक्रनाइज़ेशन के संदर्भ में "होल्ड" करता है, जबकि यह TryThis()
विधि के पूरा होने की प्रतीक्षा कर रहा है, इस प्रकार एक परिपत्र प्रतीक्षा का कारण बनता है।
इसे ठीक करने के लिए, कोड को संशोधित किया जाना चाहिए
private async void button1_Click(object sender, EventArgs e)
{
bool result = await TryThis();
Trace.TraceInformation("Done with result");
}
नोट: ईवेंट हैंडलर एकमात्र ऐसी जगह है जहाँ async void
का उपयोग किया जाना चाहिए (क्योंकि आप async void
विधि की प्रतीक्षा नहीं कर सकते हैं)।
यदि यह मशीन को अतिरिक्त काम करने की अनुमति देता है, तो Async / प्रतीक्षा में केवल प्रदर्शन में सुधार होगा
निम्नलिखित कोड पर विचार करें:
public async Task MethodA()
{
await MethodB();
// Do other work
}
public async Task MethodB()
{
await MethodC();
// Do other work
}
public async Task MethodC()
{
// Or await some other async work
await Task.Delay(100);
}
इससे बेहतर कोई प्रदर्शन नहीं होगा
public void MethodA()
{
MethodB();
// Do other work
}
public void MethodB()
{
MethodC();
// Do other work
}
public void MethodC()
{
Thread.Sleep(100);
}
Async / प्रतीक्षा का प्राथमिक उद्देश्य मशीन को अतिरिक्त काम करने की अनुमति देना है - उदाहरण के लिए, कॉलिंग थ्रेड को अन्य कार्य करने की अनुमति देने के लिए, जबकि यह कुछ I / O ऑपरेशन के परिणाम की प्रतीक्षा कर रहा है। इस स्थिति में, कॉलिंग थ्रेड को कभी भी अधिक काम करने की अनुमति नहीं दी जाती है क्योंकि यह अन्यथा करने में सक्षम होता है, इसलिए केवल MethodA()
, MethodB()
, और MethodC()
को MethodA()
रूप से कॉल करने पर कोई प्रदर्शन लाभ नहीं होता है।