C# Language
Async / प्रतीक्षा, पृष्ठभूमिकार्य, टास्क और थ्रेड उदाहरण
खोज…
टिप्पणियों
इनमें से किसी भी उदाहरण को चलाने के लिए बस उन्हें इस तरह से कॉल करें:
static void Main()
{
new Program().ProcessDataAsync();
Console.ReadLine();
}
ASP.NET Await कॉन्फ़िगर करें
जब ASP.NET एक अनुरोध को संभालता है, तो थ्रेड पूल से एक थ्रेड असाइन किया जाता है और एक अनुरोध संदर्भ बनाया जाता है। अनुरोध के संदर्भ में वर्तमान अनुरोध के बारे में जानकारी होती है जिसे स्थिर HttpContext.Current
संपत्ति के माध्यम से एक्सेस किया जा सकता है। अनुरोध के लिए अनुरोध संदर्भ तब अनुरोध को संभालने वाले थ्रेड को सौंपा गया है।
एक दिया गया अनुरोध संदर्भ एक समय में केवल एक थ्रेड पर सक्रिय हो सकता है ।
जब निष्पादन का await
जाता है, तो थ्रेड को पूल में वापस भेज दिया जाता है, जबकि एसिंक्रोनस विधि चलती है और एक अन्य थ्रेड का उपयोग करने के लिए अनुरोध संदर्भ मुक्त होता है।
public async Task<ActionResult> Index()
{
// Execution on the initially assigned thread
var products = await dbContext.Products.ToListAsync();
// Execution resumes on a "random" thread from the pool
// Execution continues using the original request context.
return View(products);
}
जब कार्य पूरा हो जाता है तो थ्रेड पूल अनुरोध के निष्पादन को जारी रखने के लिए एक और धागा प्रदान करता है। अनुरोध संदर्भ तब इस थ्रेड को असाइन किया गया है। यह मूल धागा हो भी सकता है और नहीं भी।
ब्लॉक कर रहा है
जब एक async
विधि कॉल के परिणाम के लिए प्रतीक्षा की जाती है तो सिंक्रोनस डेडलॉक उत्पन्न हो सकते हैं। उदाहरण के लिए IndexSync()
कहा जाता है, तो निम्न कोड एक गतिरोध में परिणाम होगा:
public async Task<ActionResult> Index()
{
// Execution on the initially assigned thread
List<Product> products = await dbContext.Products.ToListAsync();
// Execution resumes on a "random" thread from the pool
return View(products);
}
public ActionResult IndexSync()
{
Task<ActionResult> task = Index();
// Block waiting for the result synchronously
ActionResult result = Task.Result;
return result;
}
ऐसा इसलिए है, क्योंकि डिफ़ॉल्ट रूप से प्रतीक्षित कार्य, इस मामले में db.Products.ToListAsync()
संदर्भ को कैप्चर करेगा (ASP.NET अनुरोध संदर्भ के मामले में) और इसे पूरा होने के बाद उपयोग करने का प्रयास करें।
जब पूरा कॉल स्टैक अतुल्यकालिक होता है, तो कोई समस्या नहीं होती है, क्योंकि एक बार await
बाद मूल थ्रेड रिलीज़ होता है, अनुरोध संदर्भ को मुक्त करता है।
जब हम Task.Result
या Task.Wait()
(या अन्य ब्लॉकिंग विधि) का उपयोग करके सिंक्रोनाइज़ करते हैं, तो मूल थ्रेड अभी भी सक्रिय है और अनुरोध संदर्भ को बरकरार रखता है। प्रतीक्षित विधि अभी भी अतुल्यकालिक रूप से संचालित होती है और एक बार कॉलबैक चलाने की कोशिश करता है, अर्थात जब एक बार प्रतीक्षित कार्य वापस आ जाता है, तो यह अनुरोध संदर्भ प्राप्त करने का प्रयास करता है।
इसलिए गतिरोध उत्पन्न होता है क्योंकि अनुरोध संदर्भ के साथ अवरोधन धागा पूरा होने के लिए अतुल्यकालिक ऑपरेशन की प्रतीक्षा कर रहा है, अतुल्यकालिक ऑपरेशन पूरा करने के लिए अनुरोध संदर्भ प्राप्त करने का प्रयास कर रहा है।
ConfigureAwait
एक अपेक्षित कार्य के लिए डिफ़ॉल्ट कॉल से वर्तमान संदर्भ कैप्चर हो जाएगा और एक बार पूरा होने पर संदर्भ पर निष्पादन फिर से शुरू करने का प्रयास करेगा।
ConfigureAwait(false)
का उपयोग करके इसे रोका जा सकता है और गतिरोध से बचा जा सकता है।
public async Task<ActionResult> Index()
{
// Execution on the initially assigned thread
List<Product> products = await dbContext.Products.ToListAsync().ConfigureAwait(false);
// Execution resumes on a "random" thread from the pool without the original request context
return View(products);
}
public ActionResult IndexSync()
{
Task<ActionResult> task = Index();
// Block waiting for the result synchronously
ActionResult result = Task.Result;
return result;
}
यह अतुल्यकालिक कोड पर ब्लॉक करने के लिए आवश्यक होने पर गतिरोध से बच सकता है, हालांकि यह निरंतरता में संदर्भ को खोने की लागत पर आता है (कॉल का इंतजार करने के बाद कोड)।
ASP.NET में इसका मतलब यह है कि यदि आपका कोड कुछ कॉल का await someTask.ConfigureAwait(false);
करने के लिए निम्नलिखित है। await someTask.ConfigureAwait(false);
जानकारी को संदर्भ से एक्सेस करने का प्रयास, उदाहरण के लिए HttpContext.Current.User
तब जानकारी खो गई है। इस मामले में HttpContext.Current
शून्य है। उदाहरण के लिए:
public async Task<ActionResult> Index()
{
// Contains information about the user sending the request
var user = System.Web.HttpContext.Current.User;
using (var client = new HttpClient())
{
await client.GetAsync("http://google.com").ConfigureAwait(false);
}
// Null Reference Exception, Current is null
var user2 = System.Web.HttpContext.Current.User;
return View();
}
यदि ConfigureAwait(true)
का उपयोग किया जाता है (बिल्कुल नहीं ConfigureAwait होने के बराबर) तो user
और user2
दोनों एक ही डेटा के साथ आबादी वाले हैं।
इस कारण से अक्सर पुस्तकालय कोड में ConfigureAwait(false)
का उपयोग करने की सिफारिश की जाती है जहां संदर्भ का उपयोग नहीं किया जाता है।
Async / इंतजार
नीचे दिए गए समय का उपयोग करने के विकल्प को बनाए रखने की आवश्यकता नहीं है, जबकि कुछ अन्य सामान करने के विकल्प को बनाए रखने की पृष्ठभूमि प्रक्रिया में कुछ समय गहन सामान का उपयोग करने के लिए async / प्रतीक्षा करने के तरीके का एक सरल उदाहरण के लिए नीचे देखें।
हालांकि, यदि आपको बाद में समय गहन विधि के परिणाम के साथ काम करने की आवश्यकता है, तो आप निष्पादन का इंतजार करके ऐसा कर सकते हैं।
public async Task ProcessDataAsync()
{
// Start the time intensive method
Task<int> task = TimeintensiveMethod(@"PATH_TO_SOME_FILE");
// Control returns here before TimeintensiveMethod returns
Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
// Wait for TimeintensiveMethod to complete and get its result
int x = await task;
Console.WriteLine("Count: " + x);
}
private async Task<int> TimeintensiveMethod(object file)
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(file.ToString()))
{
string s = await reader.ReadToEndAsync();
for (int i = 0; i < 10000; i++)
s.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
// return something as a "result"
return new Random().Next(100);
}
BackgroundWorker
पृष्ठभूमि थ्रेड में समय-गहन संचालन करने के लिए BackgroundWorker
ऑब्जेक्ट का उपयोग करने के तरीके के एक सरल उदाहरण के लिए नीचे देखें।
आपको:
- एक कार्यकर्ता पद्धति को परिभाषित करें जो समय-गहन कार्य करता है और इसे एक
BackgroundWorker
वर्कर से एकBackgroundWorker
वर्कर के लिए एकDoWork
कहता है। -
RunWorkerAsync
साथ निष्पादन शुरू करें। कार्यकर्ता से जुड़ी विधि द्वारा अपेक्षित कोई तर्कDoWork
के माध्यम से में पारित किया जा सकताDoWorkEventArgs
पैरामीटरRunWorkerAsync
।
DoWork
घटना के अलावा BackgroundWorker
वर्ग दो घटनाओं को भी परिभाषित करता है जिनका उपयोग यूजर इंटरफेस के साथ बातचीत के लिए किया जाना चाहिए। ये वैकल्पिक हैं।
-
RunWorkerCompleted
जब घटना शुरू हो रहा हैDoWork
संचालकों पूरा कर लिया है। -
ProgressChanged
जब घटना शुरू हो रहा हैReportProgress
विधि कहा जाता है।
public void ProcessDataAsync()
{
// Start the time intensive method
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += BwDoWork;
bw.RunWorkerCompleted += BwRunWorkerCompleted;
bw.RunWorkerAsync(@"PATH_TO_SOME_FILE");
// Control returns here before TimeintensiveMethod returns
Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
}
// Method that will be called after BwDoWork exits
private void BwRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// we can access possible return values of our Method via the Parameter e
Console.WriteLine("Count: " + e.Result);
}
// execution of our time intensive Method
private void BwDoWork(object sender, DoWorkEventArgs e)
{
e.Result = TimeintensiveMethod(e.Argument);
}
private int TimeintensiveMethod(object file)
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(file.ToString()))
{
string s = reader.ReadToEnd();
for (int i = 0; i < 10000; i++)
s.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
// return something as a "result"
return new Random().Next(100);
}
कार्य
पृष्ठभूमि प्रक्रिया में कुछ समय गहन सामान करने के लिए Task
का उपयोग करने के एक सरल उदाहरण के लिए नीचे देखें।
आपको बस एक Task.Run()
में अपना समय गहन विधि लपेटने की जरूरत है। Task.Run()
कॉल करें।
public void ProcessDataAsync()
{
// Start the time intensive method
Task<int> t = Task.Run(() => TimeintensiveMethod(@"PATH_TO_SOME_FILE"));
// Control returns here before TimeintensiveMethod returns
Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
Console.WriteLine("Count: " + t.Result);
}
private int TimeintensiveMethod(object file)
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(file.ToString()))
{
string s = reader.ReadToEnd();
for (int i = 0; i < 10000; i++)
s.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
// return something as a "result"
return new Random().Next(100);
}
धागा
पृष्ठभूमि प्रक्रिया में कुछ समय गहन सामान करने के लिए Thread
का उपयोग करने के एक सरल उदाहरण के लिए नीचे देखें।
public async void ProcessDataAsync()
{
// Start the time intensive method
Thread t = new Thread(TimeintensiveMethod);
// Control returns here before TimeintensiveMethod returns
Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
}
private void TimeintensiveMethod()
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(@"PATH_TO_SOME_FILE"))
{
string v = reader.ReadToEnd();
for (int i = 0; i < 10000; i++)
v.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
}
जैसा कि आप देख सकते हैं कि हम अपने TimeIntensiveMethod
से कोई मान नहीं लौटा सकते क्योंकि Thread
अपने पैरामीटर के रूप में एक शून्य विधि की अपेक्षा करता है।
Thread
से वापसी मान प्राप्त करने के लिए या तो एक घटना या निम्न का उपयोग करें:
int ret;
Thread t= new Thread(() =>
{
Console.WriteLine("Start TimeintensiveMethod.");
// Do some time intensive calculations...
using (StreamReader reader = new StreamReader(file))
{
string s = reader.ReadToEnd();
for (int i = 0; i < 10000; i++)
s.GetHashCode();
}
Console.WriteLine("End TimeintensiveMethod.");
// return something to demonstrate the coolness of await-async
ret = new Random().Next(100);
});
t.Start();
t.Join(1000);
Console.Writeline("Count: " + ret);
टास्क "रन और भूल" विस्तार
कुछ मामलों में (जैसे लॉगिंग) यह कार्य चलाने के लिए उपयोगी हो सकता है और परिणाम के लिए इंतजार नहीं करेगा। निम्नलिखित एक्सटेंशन कार्य चलाने और बाकी कोड के निष्पादन को जारी रखने की अनुमति देता है:
public static class TaskExtensions
{
public static async void RunAndForget(
this Task task, Action<Exception> onException = null)
{
try
{
await task;
}
catch (Exception ex)
{
onException?.Invoke(ex);
}
}
}
परिणाम केवल विस्तार विधि के अंदर प्रतीक्षित है। चूंकि async
/ await
का उपयोग किया जाता है, इसलिए अपवाद को पकड़ना और इसे संभालने के लिए एक वैकल्पिक विधि को कॉल करना संभव है।
एक्सटेंशन का उपयोग करने का एक उदाहरण:
var task = Task.FromResult(0); // Or any other task from e.g. external lib.
task.RunAndForget(
e =>
{
// Something went wrong, handle it.
});