ระหว่างเปลี่ยนเป็น. NET Core 3 IAsynsDisposable
ใหม่ฉันได้พบกับปัญหาต่อไปนี้
แกนหลักของปัญหา: ถ้าDisposeAsync
มีข้อยกเว้นข้อยกเว้นนี้ซ่อนข้อยกเว้นใด ๆ ที่ถูกโยนลงในawait using
-block
class Program
{
static async Task Main()
{
try
{
await using (var d = new D())
{
throw new ArgumentException("I'm inside using");
}
}
catch (Exception e)
{
Console.WriteLine(e.Message); // prints I'm inside dispose
}
}
}
class D : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await Task.Delay(1);
throw new Exception("I'm inside dispose");
}
}
สิ่งที่จะถูกจับได้คือข้อAsyncDispose
ยกเว้นถ้ามันถูกโยนทิ้งและข้อยกเว้นจากภายในawait using
ก็ต่อเมื่อAsyncDispose
ไม่ได้โยน
อย่างไรก็ตามฉันจะชอบมันมากกว่าวิธีอื่น: รับข้อยกเว้นจากawait using
บล็อกถ้าเป็นไปได้และDisposeAsync
ยกเว้นเฉพาะในกรณีที่await using
บล็อกเสร็จสมบูรณ์
เหตุผล: ลองนึกภาพว่าชั้นเรียนของฉันD
ทำงานกับทรัพยากรเครือข่ายบางส่วนและสมัครรับการแจ้งเตือนทางไกล รหัสภายในawait using
สามารถทำอะไรผิดพลาดและทำให้ช่องทางการสื่อสารล้มเหลวหลังจากนั้นรหัสในการกำจัดซึ่งพยายามปิดการสื่อสารอย่างนุ่มนวล (เช่นการยกเลิกการสมัครรับการแจ้งเตือน) ก็จะล้มเหลวเช่นกัน แต่ข้อยกเว้นแรกให้ข้อมูลจริงเกี่ยวกับปัญหาและข้อที่สองเป็นเพียงปัญหารอง
ในอีกกรณีหนึ่งเมื่อส่วนหลักวิ่งผ่านและการกำจัดล้มเหลวปัญหาที่แท้จริงคือภายในDisposeAsync
ดังนั้นข้อยกเว้นจากDisposeAsync
เป็นส่วนที่เกี่ยวข้อง ซึ่งหมายความว่าเพียงระงับการยกเว้นทั้งหมดภายในDisposeAsync
ไม่ควรเป็นความคิดที่ดี
ฉันรู้ว่ามีปัญหาเดียวกันกับกรณีที่ไม่ใช่ async: ข้อยกเว้นในfinally
การแทนที่ข้อยกเว้นในที่ว่าทำไมมันไม่แนะนำให้โยนในtry
Dispose()
แต่ด้วยคลาสที่เข้าถึงเครือข่ายระงับข้อยกเว้นในวิธีการปิดไม่ได้ดูดีเลย
เป็นไปได้ที่จะแก้ไขปัญหาด้วยผู้ช่วยดังต่อไปนี้:
static class AsyncTools
{
public static async Task UsingAsync<T>(this T disposable, Func<T, Task> task)
where T : IAsyncDisposable
{
bool trySucceeded = false;
try
{
await task(disposable);
trySucceeded = true;
}
finally
{
if (trySucceeded)
await disposable.DisposeAsync();
else // must suppress exceptions
try { await disposable.DisposeAsync(); } catch { }
}
}
}
และใช้มันเหมือน
await new D().UsingAsync(d =>
{
throw new ArgumentException("I'm inside using");
});
ซึ่งเป็นประเภทที่น่าเกลียด (และไม่อนุญาตให้สิ่งต่าง ๆ เช่นผลตอบแทนเร็วภายในบล็อกที่ใช้)
มีวิธีแก้ปัญหาที่ดีและเป็นที่ยอมรับawait using
ถ้าเป็นไปได้? การค้นหาในอินเทอร์เน็ตของฉันไม่พบแม้แต่การพูดคุยปัญหานี้
CloseAsync
ที่ฉันต้องใช้ความระมัดระวังเป็นพิเศษเพื่อให้มันทำงานได้ ถ้าฉันเพิ่งวางไว้ที่ส่วนท้ายของusing
-block มันจะถูกข้ามไปที่ผลตอบแทนเร็วเป็นต้น (นี่คือสิ่งที่เราต้องการจะเกิดขึ้น) และข้อยกเว้น (นี่คือสิ่งที่เราต้องการจะเกิดขึ้น) แต่ความคิดนั้นดูดี
Dispose
ได้รับเสมอ "สิ่งที่อาจผิดพลาด: เพียงแค่ทำดีที่สุดของคุณเพื่อปรับปรุงสถานการณ์ แต่ไม่ได้ทำให้แย่ลง" และฉันไม่เห็นว่าทำไมAsyncDispose
ควรจะแตกต่างกัน
DisposeAsync
เป็นระเบียบเรียบร้อยดีที่สุดแต่การไม่โยนเป็นสิ่งที่ถูกต้อง คุณได้รับการพูดคุยเกี่ยวกับความตั้งใจที่จะให้ผลตอบแทนในช่วงต้นที่ผลตอบแทนในช่วงต้นเจตนาอาจผิดพลาดบายพาสโทรCloseAsync
: เหล่านั้นจะถูกคนที่ต้องห้ามตามมาตรฐานการเข้ารหัสจำนวนมาก
Close
วิธีแยกต่างหากด้วยเหตุผลนี้ อาจเป็นการดีที่จะทำเช่นเดียวกัน:CloseAsync
พยายามปิดสิ่งต่าง ๆ ลงอย่างดีและล้มเหลวDisposeAsync
ทำได้ดีที่สุดและล้มเหลวอย่างเงียบ ๆ