ฉันต้องการเขียนวิธีการซิงค์พร้อมout
พารามิเตอร์เช่นนี้
public async void Method1()
{
int op;
int result = await GetDataTaskAsync(out op);
}
ฉันจะทำสิ่งนี้ได้GetDataTaskAsync
อย่างไร
ฉันต้องการเขียนวิธีการซิงค์พร้อมout
พารามิเตอร์เช่นนี้
public async void Method1()
{
int op;
int result = await GetDataTaskAsync(out op);
}
ฉันจะทำสิ่งนี้ได้GetDataTaskAsync
อย่างไร
คำตอบ:
คุณไม่สามารถมีวิธีการซิงค์กับพารามิเตอร์ref
หรือout
Lucian Wischik อธิบายว่าทำไมจึงเป็นไปไม่ได้ในเธรด MSDN นี้: http://social.msdn.microsoft.com/Forums/en-US/d2f48a52-e35a-4948-844d-828a1a6deb74/why-async-methods-cannot-have -ref หรือออกพารามิเตอร์
ทำไมเมธอด async จึงไม่รองรับพารามิเตอร์แบบอ้างอิงอ้างอิง? (หรือพารามิเตอร์อ้างอิง?) นั่นคือข้อ จำกัด ของ CLR เราเลือกที่จะใช้วิธีการแบบอะซิงโครนัสในวิธีที่คล้ายกับวิธีตัววนซ้ำ - เช่นผ่านคอมไพเลอร์เปลี่ยนวิธีการให้เป็นสถานะเครื่องวัตถุ CLR ไม่มีวิธีที่ปลอดภัยในการจัดเก็บที่อยู่ของ "พารามิเตอร์ออก" หรือ "พารามิเตอร์อ้างอิง" เป็นฟิลด์ของวัตถุ วิธีเดียวที่จะได้รับการสนับสนุนพารามิเตอร์ out-by-reference คือถ้าคุณลักษณะ async ถูกทำโดย CLR ระดับต่ำเขียนใหม่แทนคอมไพเลอร์เขียนใหม่ เราตรวจสอบวิธีการนั้นและมีหลายสิ่งที่เกิดขึ้น แต่ในที่สุดมันก็มีค่าใช้จ่ายสูงจนไม่เคยเกิดขึ้น
วิธีแก้ปัญหาทั่วไปสำหรับสถานการณ์นี้คือการให้เมธอด 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
มันเป็นเรื่องที่น่าเกลียดมี : P
คุณไม่สามารถ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 +คือการใช้ไวยากรณ์ของ tuple โดยปริยาย
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
พารามิเตอร์ซึ่งเราทุกคนรู้ไม่ได้รับอนุญาตใน C #
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
จะตรวจสอบความสำเร็จของการต่อเนื่องครั้งแรกไม่ใช่ความสำเร็จของงานต้นฉบับ
ฉันมีปัญหาเดียวกันกับที่ฉันชอบใช้วิธีลองแบบซึ่งโดยทั่วไปดูเหมือนจะไม่เข้ากันกับกระบวนทัศน์แบบ async-await-paradigm ...
สิ่งสำคัญสำหรับฉันคือฉันสามารถเรียกใช้ลองเมธอดภายใน if-clause เดียวและไม่จำเป็นต้องกำหนดตัวแปรออกล่วงหน้าไว้ก่อน แต่สามารถทำอินไลน์ได้ในตัวอย่างต่อไปนี้:
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
}
สำหรับพารามิเตอร์หลายค่าคุณสามารถกำหนด struct เพิ่มเติม (เช่น AsyncOut <T, OUT1, OUT2>) หรือคุณสามารถคืนค่า tuple
ข้อ จำกัด ของasync
วิธีการที่ไม่ยอมรับout
พารามิเตอร์จะใช้เฉพาะกับวิธีการซิงค์ที่สร้างโดยคอมไพเลอร์ซึ่งประกาศด้วย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
อะซิงก์ซับซ้อนเกินกว่าที่จะแสดงออกได้มันอาจจะถูกห่อหุ้มภายในตัวแทนที่ไม่ระบุชื่อแบบอะซิงโครนัสซ้อนกัน A TaskCompletionSource
จะยังคงจำเป็นสำหรับout
พารามิเตอร์ เป็นไปได้ว่าout
พารามิเตอร์สามารถทำให้เสร็จก่อนที่งานหลักจะเสร็จสมบูรณ์ดังตัวอย่าง:
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):
}
ต่อไปนี้เป็นรหัสของคำตอบของ @ dcastro ที่แก้ไขสำหรับ 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);
}
สำหรับรายละเอียดเกี่ยวกับ tuples ชื่อใหม่ตัวอักษร tuple และ deconstructions ของ tuple โปรดดูที่: https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/
คุณสามารถทำได้โดยใช้ TPL (task ขนานไลบรารี) แทนที่จะใช้คำหลักที่รอคอยโดยตรง
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