जवाबों:
आपके पास ref
या out
मापदंडों के साथ async विधियाँ नहीं हो सकती हैं ।
Lucian Wischik बताते हैं कि इस MSDN थ्रेड पर यह क्यों संभव नहीं है: http://social.msdn.microsoft.com/Forums/en-US/d2f48a52-e35a-4948-844d-28a1a6deb74/why-async-methods-cannot-have -ref या बाहर-पैरामीटर
के लिए के रूप में क्यों async तरीकों संदर्भ के बाहर मापदंडों का समर्थन नहीं करते? (या रेफरी पैरामीटर?) यह सीएलआर की एक सीमा है। हमने async विधियों को पुनरावृत्ति विधियों के समान तरीके से कार्यान्वित करने के लिए चुना - अर्थात संकलक के माध्यम से विधि को राज्य-मशीन-ऑब्जेक्ट में परिवर्तित करना। सीएलआर के पास ऑब्जेक्ट के क्षेत्र के रूप में "आउट पैरामीटर" या "संदर्भ पैरामीटर" के पते को संग्रहीत करने का कोई सुरक्षित तरीका नहीं है। आउट-बाय-रेफरेंस मापदंडों का समर्थन करने का एकमात्र तरीका यह होगा कि कंपाइलर-राईटाइट के बजाए निम्न-स्तरीय सीएलआर पुनर्लेखन द्वारा एस्किंक फीचर किया जाए। हमने उस दृष्टिकोण की जांच की, और इसके लिए बहुत कुछ हो रहा था, लेकिन यह अंततः इतना महंगा हो गया था कि ऐसा कभी नहीं हुआ होगा।
इस स्थिति के लिए एक विशिष्ट समाधान के लिए async विधि के बजाय एक Tuple वापस करना है। आप अपनी विधि को इस प्रकार पुनः लिख सकते हैं:
public async Task Method1()
{
var tuple = await GetDataTaskAsync();
int op = tuple.Item1;
int result = tuple.Item2;
}
public async Task<Tuple<int, int>> GetDataTaskAsync()
{
//...
return new Tuple<int, int>(1, 2);
}
Tuple
विकल्प के लिए धन्यवाद । बहुत मददगार।
Tuple
। : पी
आपके पास ref
या विधियों out
में पैरामीटर नहीं हो सकते हैं async
(जैसा कि पहले ही नोट किया गया था)।
यह चारों ओर घूम रहे डेटा में कुछ मॉडलिंग के लिए चिल्लाती है:
public class Data
{
public int Op {get; set;}
public int Result {get; set;}
}
public async void Method1()
{
Data data = await GetDataTaskAsync();
// use data.Op and data.Result from here on
}
public async Task<Data> GetDataTaskAsync()
{
var returnValue = new Data();
// Fill up returnValue
return returnValue;
}
आप अपने कोड को अधिक आसानी से पुन: उपयोग करने की क्षमता प्राप्त करते हैं, साथ ही यह चर या ट्यूपल की तुलना में अधिक पठनीय है।
C # 7 + समाधान निहित टुपल सिंटैक्स का उपयोग करना है।
private async Task<(bool IsSuccess, IActionResult Result)> TryLogin(OpenIdConnectRequest request)
{
return (true, BadRequest(new OpenIdErrorResponse
{
Error = OpenIdConnectConstants.Errors.AccessDenied,
ErrorDescription = "Access token provided is not valid."
}));
}
वापसी परिणाम विधि हस्ताक्षर परिभाषित संपत्ति के नाम का उपयोग करता है। उदाहरण के लिए:
var foo = await TryLogin(request);
if (foo.IsSuccess)
return foo.Result;
एलेक्स ने पठनीयता पर एक महान बिंदु बनाया। समान रूप से, एक फ़ंक्शन भी इंटरफ़ेस को परिभाषित करने के लिए पर्याप्त है जो लौटाया जा रहा है और आपको सार्थक चर नाम भी मिलते हैं।
delegate void OpDelegate(int op);
Task<bool> GetDataTaskAsync(OpDelegate callback)
{
bool canGetData = true;
if (canGetData) callback(5);
return Task.FromResult(canGetData);
}
कॉल करने वालों को एक लैम्बडा (या एक नामांकित फ़ंक्शन) प्रदान करता है और प्रतिनिधि से चर नाम (ओं) को कॉपी करके इंटैलिजेंस मदद करता है।
int myOp;
bool result = await GetDataTaskAsync(op => myOp = op);
यह विशेष दृष्टिकोण एक "कोशिश" विधि की तरह है जहां myOp
विधि परिणाम होने पर सेट किया जाता है true
। अन्यथा, आप परवाह नहीं करते हैं myOp
।
out
मापदंडों की एक अच्छी विशेषता यह है कि उनका उपयोग डेटा वापस करने के लिए भी किया जा सकता है जब कोई फ़ंक्शन अपवाद फेंकता है। मुझे लगता है कि async
विधि के साथ ऐसा करने के लिए निकटतम समकक्ष डेटा को धारण करने के लिए एक नई वस्तु का उपयोग कर रहा होगा जिसे async
विधि और कॉलर दोनों संदर्भित कर सकते हैं। एक अन्य तरीका एक प्रतिनिधि को पारित करना होगा जैसा कि दूसरे उत्तर में सुझाया गया है ।
ध्यान दें कि इन तकनीकों में से किसी में भी संकलक से किसी भी प्रकार का प्रवर्तन नहीं होगा out
। यानी, संकलक को आपको साझा किए गए ऑब्जेक्ट पर मान सेट करने या प्रतिनिधि में एक पास कॉल करने की आवश्यकता नहीं होगी।
एक साझा ऑब्जेक्ट का उपयोग करने के लिए एक उदाहरण कार्यान्वयन है, जहां तरीकों और अन्य विभिन्न परिदृश्यों के लिए नकल करने ref
और out
उपयोग करने के लिए async
जहां ref
और out
उपलब्ध नहीं हैं:
class Ref<T>
{
// Field rather than a property to support passing to functions
// accepting `ref T` or `out T`.
public T Value;
}
async Task OperationExampleAsync(Ref<int> successfulLoopsRef)
{
var things = new[] { 0, 1, 2, };
var i = 0;
while (true)
{
// Fourth iteration will throw an exception, but we will still have
// communicated data back to the caller via successfulLoopsRef.
things[i] += i;
successfulLoopsRef.Value++;
i++;
}
}
async Task UsageExample()
{
var successCounterRef = new Ref<int>();
// Note that it does not make sense to access successCounterRef
// until OperationExampleAsync completes (either fails or succeeds)
// because there’s no synchronization. Here, I think of passing
// the variable as “temporarily giving ownership” of the referenced
// object to OperationExampleAsync. Deciding on conventions is up to
// you and belongs in documentation ^^.
try
{
await OperationExampleAsync(successCounterRef);
}
finally
{
Console.WriteLine($"Had {successCounterRef.Value} successful loops.");
}
}
मुझे Try
पैटर्न पसंद है। यह एक साफ पैटर्न है।
if (double.TryParse(name, out var result))
{
// handle success
}
else
{
// handle error
}
लेकिन, इसके साथ चुनौतीपूर्ण है async
। इसका मतलब यह नहीं है कि हमारे पास वास्तविक विकल्प नहीं हैं। यहाँ तीन कोर दृष्टिकोण हैं जो आप पैटर्न के async
अर्ध-संस्करण में तरीकों के लिए विचार कर सकते हैं Try
।
यह एक सिंक Try
विधि की तरह दिखता है, केवल एक के tuple
बजाय एक के bool
साथ लौट रहा हैout
पैरामीटर है, जो हम सब पता है कि सी # में अनुमति नहीं है।
var result = await DoAsync(name);
if (result.Success)
{
// handle success
}
else
{
// handle error
}
एक ऐसी विधि के साथ जो रिटर्न करती true
हैfalse
और कभी भी किसी फेंकता है exception
।
याद रखें, एक अपवाद को एक
Try
विधि में फेंकने से पैटर्न का पूरा उद्देश्य टूट जाता है।
async Task<(bool Success, StorageFile File, Exception exception)> DoAsync(string fileName)
{
try
{
var folder = ApplicationData.Current.LocalCacheFolder;
return (true, await folder.GetFileAsync(fileName), null);
}
catch (Exception exception)
{
return (false, null, exception);
}
}
हम anonymous
बाहरी चर सेट करने के तरीकों का उपयोग कर सकते हैं । यह चालाक वाक्यविन्यास है, हालांकि थोड़ा जटिल है। छोटी खुराक में, यह ठीक है।
var file = default(StorageFile);
var exception = default(Exception);
if (await DoAsync(name, x => file = x, x => exception = x))
{
// handle success
}
else
{
// handle failure
}
Try
पद्धति पैटर्न की मूल बातों का पालन करती है लेकिन out
कॉलबैक विधियों में पारित करने के लिए पैरामीटर सेट करती है । यह इस तरह किया जाता है।
async Task<bool> DoAsync(string fileName, Action<StorageFile> file, Action<Exception> error)
{
try
{
var folder = ApplicationData.Current.LocalCacheFolder;
file?.Invoke(await folder.GetFileAsync(fileName));
return true;
}
catch (Exception exception)
{
error?.Invoke(exception);
return false;
}
}
यहां प्रदर्शन के बारे में मेरे मन में एक सवाल है। लेकिन, C # कंपाइलर इतना भयावह स्मार्ट है, कि मुझे लगता है कि आप सुरक्षित हैं इस विकल्प को चुनना, लगभग सुनिश्चित करना।
क्या होगा यदि आप बस के TPL
रूप में डिजाइन का उपयोग करें ? कोई तुक नहीं। यहां विचार यह है कि हम अपवादों का उपयोग ContinueWith
दो अलग-अलग रास्तों पर पुनर्निर्देशित करने के लिए करते हैं।
await DoAsync(name).ContinueWith(task =>
{
if (task.Exception != null)
{
// handle fail
}
if (task.Result is StorageFile sf)
{
// handle success
}
});
एक ऐसी विधि के साथ जो exception
किसी भी प्रकार की विफलता होने पर फेंकता है। यह एक लौटने से अलग है boolean
। यह एक तरह से संवाद है TPL
।
async Task<StorageFile> DoAsync(string fileName)
{
var folder = ApplicationData.Current.LocalCacheFolder;
return await folder.GetFileAsync(fileName);
}
उपरोक्त कोड में, यदि फ़ाइल नहीं मिली है, तो एक अपवाद फेंक दिया गया है। यह उस विफलता का आह्वान ContinueWith
करेगा Task.Exception
जो इसके लॉजिक ब्लॉक में काम करेगी। नीट, हुह?
सुनो, एक कारण है कि हम
Try
पैटर्न से प्यार करते हैं । यह मूल रूप से इतना साफ और पठनीय है और, परिणामस्वरूप, बनाए रखने योग्य है। जैसा कि आप अपना दृष्टिकोण चुनते हैं, पठनीयता के लिए वॉचडॉग। अगले डेवलपर को याद रखें जो 6 महीने में है और आपके पास स्पष्ट सवालों के जवाब देने के लिए नहीं है। आपका कोड एकमात्र दस्तावेज हो सकता है जो एक डेवलपर के पास होगा।
शुभकामनाएँ।
ContinueWith
कॉलिंग कॉल के अपेक्षित परिणाम हैं? मेरी समझ के अनुसार दूसरा ContinueWith
पहली निरंतरता की सफलता की जांच करेगा, मूल कार्य की सफलता की नहीं।
मुझे भी यही समस्या थी कि मैं ट्राई-मेथड-पैटर्न का उपयोग करना पसंद करता हूं जो मूल रूप से एसिंक्स-वेट-प्रतिमान के लिए असंगत लगता है ...
मेरे लिए महत्वपूर्ण यह है कि मैं एक एकल-खंड के भीतर ट्राय-मेथड को कॉल कर सकता हूं और इससे पहले आउट-वेरिएबल्स को पूर्व-परिभाषित करने की आवश्यकता नहीं है, लेकिन इसे निम्न उदाहरण की तरह इन-लाइन कर सकते हैं:
if (TryReceive(out string msg))
{
// use msg
}
तो मैं निम्नलिखित समाधान के साथ आया:
एक सहायक संरचना को परिभाषित करें:
public struct AsyncOut<T, OUT>
{
private readonly T returnValue;
private readonly OUT result;
public AsyncOut(T returnValue, OUT result)
{
this.returnValue = returnValue;
this.result = result;
}
public T Out(out OUT result)
{
result = this.result;
return returnValue;
}
public T ReturnValue => returnValue;
public static implicit operator AsyncOut<T, OUT>((T returnValue ,OUT result) tuple) =>
new AsyncOut<T, OUT>(tuple.returnValue, tuple.result);
}
इस तरह से async कोशिश-विधि को परिभाषित करें:
public async Task<AsyncOut<bool, string>> TryReceiveAsync()
{
string message;
bool success;
// ...
return (success, message);
}
इस तरह async कोशिश-विधि को बुलाओ:
if ((await TryReceiveAsync()).Out(out string msg))
{
// use msg
}
कई आउट पैरामीटर के लिए आप अतिरिक्त संरचनाएं (जैसे AsyncOut <T, OUT1, OUT2>) को परिभाषित कर सकते हैं या आप एक टपल लौटा सकते हैं।
मापदंडों को async
स्वीकार नहीं करने वाले तरीकों की सीमा out
केवल कंपाइलर-जेनरेट किए गए एसिंक्स तरीकों पर लागू होती है, इन्हें async
कीवर्ड के साथ घोषित किया जाता है । यह हाथ से तैयार किए गए async तरीकों पर लागू नहीं होता है। दूसरे शब्दों में मापदंडों को Task
स्वीकार करते हुए वापसी के तरीके बनाना संभव है out
। उदाहरण के लिए हम कहते हैं कि हमारे पास पहले से ही एक ParseIntAsync
विधि है जो फेंकता है, और हम एक TryParseIntAsync
ऐसा बनाना चाहते हैं जो फेंक नहीं देता है। हम इसे इस तरह लागू कर सकते हैं:
public static Task<bool> TryParseIntAsync(string s, out Task<int> result)
{
var tcs = new TaskCompletionSource<int>();
result = tcs.Task;
return ParseIntAsync(s).ContinueWith(t =>
{
if (t.IsFaulted)
{
tcs.SetException(t.Exception.InnerException);
return false;
}
tcs.SetResult(t.Result);
return true;
}, default, TaskContinuationOptions.None, TaskScheduler.Default);
}
विधि TaskCompletionSource
और ContinueWith
विधि का उपयोग करना थोड़ा अजीब है, लेकिन कोई अन्य विकल्प नहीं है क्योंकि हम सुविधाजनक का उपयोग नहीं कर सकते हैंawait
इस पद्धति के अंदर कीवर्ड का ।
उपयोग उदाहरण:
if (await TryParseIntAsync("-13", out var result))
{
Console.WriteLine($"Result: {await result}");
}
else
{
Console.WriteLine($"Parse failed");
}
अद्यतन: यदि एसिंक्स तर्क बिना व्यक्त किए जाने के लिए बहुत जटिल है await
, तो इसे एक नेस्टेड अतुल्यकालिक प्रतिनिधि प्रतिनिधि के अंदर समझाया जा सकता है। एक TaskCompletionSource
अभी भी के लिए जरूरत होगी out
पैरामीटर। यह संभव है कि out
पैरामीटर मुख्य कार्य के पूरा होने से पहले पूरा किया जा सकता है, जैसा कि उदाहरण bellow में:
public static Task<string> GetDataAsync(string url, out Task<int> rawDataLength)
{
var tcs = new TaskCompletionSource<int>();
rawDataLength = tcs.Task;
return ((Func<Task<string>>)(async () =>
{
var response = await GetResponseAsync(url);
var rawData = await GetRawDataAsync(response);
tcs.SetResult(rawData.Length);
return await FilterDataAsync(rawData);
}))();
}
यह उदाहरण तीन अतुल्यकालिक तरीकों के अस्तित्व को मानता है GetResponseAsync
, GetRawDataAsync
और FilterDataAsync
जिसे उत्तराधिकार में कहा जाता है। out
पैरामीटर दूसरी विधि के पूरा होने पर पूरा हो गया है। GetDataAsync
विधि इस तरह इस्तेमाल किया जा सकता:
var data = await GetDataAsync("http://example.com", out var rawDataLength);
Console.WriteLine($"Data: {data}");
Console.WriteLine($"RawDataLength: {await rawDataLength}");
प्रतीक्षा करने data
से पहले प्रतीक्षा करना rawDataLength
इस सरलीकृत उदाहरण में महत्वपूर्ण है, क्योंकि अपवाद के मामले में out
पैरामीटर कभी पूरा नहीं होगा।
मुझे लगता है कि इस तरह से ValueTuples का उपयोग करना काम कर सकता है। आपको पहले हालांकि ValueTuple NuGet पैकेज जोड़ना होगा:
public async void Method1()
{
(int op, int result) tuple = await GetDataTaskAsync();
int op = tuple.op;
int result = tuple.result;
}
public async Task<(int op, int result)> GetDataTaskAsync()
{
int x = 5;
int y = 10;
return (op: x, result: y):
}
यहाँ नाम के कोड @ 7.0 के लिए C # 7.0 के लिए संशोधित किया गया है जिसका नाम tuples और tuple deconstruction है, जो अंकन को सुव्यवस्थित करता है:
public async void Method1()
{
// Version 1, named tuples:
// just to show how it works
/*
var tuple = await GetDataTaskAsync();
int op = tuple.paramOp;
int result = tuple.paramResult;
*/
// Version 2, tuple deconstruction:
// much shorter, most elegant
(int op, int result) = await GetDataTaskAsync();
}
public async Task<(int paramOp, int paramResult)> GetDataTaskAsync()
{
//...
return (1, 2);
}
टुपल्स, टुपल लिटरल और टपल डेकोस्ट्रक्शन के नए नाम के बारे में जानकारी के लिए देखें: https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/
आप प्रतीक्षित कीवर्ड का उपयोग करते हुए प्रत्यक्ष के बजाय टीपीएल (टास्क समानांतर लाइब्रेरी) का उपयोग करके ऐसा कर सकते हैं।
private bool CheckInCategory(int? id, out Category category)
{
if (id == null || id == 0)
category = null;
else
category = Task.Run(async () => await _context.Categories.FindAsync(id ?? 0)).Result;
return category != null;
}
if(!CheckInCategory(int? id, out var category)) return error