วิธีที่เหมาะสมในการจัดการกับข้อยกเว้นใน AsyncDispose


20

ระหว่างเปลี่ยนเป็น. 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ถ้าเป็นไปได้? การค้นหาในอินเทอร์เน็ตของฉันไม่พบแม้แต่การพูดคุยปัญหานี้


1
" แต่ด้วยคลาสที่เข้าถึงเครือข่ายที่ระงับข้อยกเว้นในวิธีการปิดไม่ดูดีเลย " - ฉันคิดว่าคลาส BLC เครือข่ายส่วนใหญ่มีCloseวิธีแยกต่างหากด้วยเหตุผลนี้ อาจเป็นการดีที่จะทำเช่นเดียวกัน: CloseAsyncพยายามปิดสิ่งต่าง ๆ ลงอย่างดีและล้มเหลว DisposeAsyncทำได้ดีที่สุดและล้มเหลวอย่างเงียบ ๆ
canton7

@ canton7: ​​การมีวิธีแยกต่างหากCloseAsyncที่ฉันต้องใช้ความระมัดระวังเป็นพิเศษเพื่อให้มันทำงานได้ ถ้าฉันเพิ่งวางไว้ที่ส่วนท้ายของusing-block มันจะถูกข้ามไปที่ผลตอบแทนเร็วเป็นต้น (นี่คือสิ่งที่เราต้องการจะเกิดขึ้น) และข้อยกเว้น (นี่คือสิ่งที่เราต้องการจะเกิดขึ้น) แต่ความคิดนั้นดูดี
วลาด

มีเหตุผลที่มาตรฐานการเข้ารหัสจำนวนมากห้ามมิให้ส่งคืน แต่เนิ่นๆ :) ในกรณีที่ระบบเครือข่ายมีส่วนเกี่ยวข้องการมีความชัดเจนเล็กน้อยนั้นไม่ใช่เรื่องเลวร้ายสำหรับ IMO Disposeได้รับเสมอ "สิ่งที่อาจผิดพลาด: เพียงแค่ทำดีที่สุดของคุณเพื่อปรับปรุงสถานการณ์ แต่ไม่ได้ทำให้แย่ลง" และฉันไม่เห็นว่าทำไมAsyncDisposeควรจะแตกต่างกัน
canton7

@ canton7: ดีในข้อยกเว้นภาษาที่มีคำสั่งทุกอาจจะมีการกลับไปเริ่มต้น: - \
Vlad

ขวา แต่ผู้ที่จะได้รับการยกเว้น ในกรณีนี้การทำให้DisposeAsyncเป็นระเบียบเรียบร้อยดีที่สุดแต่การไม่โยนเป็นสิ่งที่ถูกต้อง คุณได้รับการพูดคุยเกี่ยวกับความตั้งใจที่จะให้ผลตอบแทนในช่วงต้นที่ผลตอบแทนในช่วงต้นเจตนาอาจผิดพลาดบายพาสโทรCloseAsync: เหล่านั้นจะถูกคนที่ต้องห้ามตามมาตรฐานการเข้ารหัสจำนวนมาก
canton7

คำตอบ:


3

มีข้อยกเว้นที่คุณต้องการแสดงให้เห็น (ขัดจังหวะคำขอปัจจุบันหรือทำให้กระบวนการล้มเหลว) และมีข้อยกเว้นที่การออกแบบของคุณคาดว่าจะเกิดขึ้นในบางครั้งและคุณสามารถจัดการได้ (เช่นลองอีกครั้งและดำเนินการต่อ)

แต่การแยกความแตกต่างระหว่างสองประเภทนี้ขึ้นอยู่กับผู้โทรที่ดีที่สุดของรหัส - นี่คือจุดทั้งหมดของข้อยกเว้นเพื่อให้การตัดสินใจขึ้นอยู่กับผู้โทร

Disposeบางครั้งผู้ที่โทรมาจะให้ความสำคัญมากขึ้นในผิวข้อยกเว้นจากการป้องกันรหัสเดิมและบางครั้งข้อยกเว้นจากที่ ไม่มีกฎทั่วไปสำหรับการตัดสินใจที่ควรให้ความสำคัญ CLR นั้นมีความสอดคล้องกันอย่างน้อย (ดังที่คุณได้บันทึกไว้) ระหว่างพฤติกรรมการซิงค์และการไม่ซิงค์

อาจโชคร้ายที่ตอนนี้เราต้องAggregateExceptionแสดงข้อยกเว้นหลายอย่างมันไม่สามารถดัดแปลงเพื่อแก้ปัญหานี้ได้ เช่นถ้าเป็นข้อยกเว้นที่มีอยู่แล้วในการบินและอื่น ๆ AggregateExceptionจะโยนพวกเขาจะรวมกันเป็น catchกลไกสามารถแก้ไขเพื่อที่ว่าถ้าคุณเขียนcatch (MyException)แล้วมันจะจับใด ๆที่มีข้อยกเว้นชนิดAggregateException MyExceptionยังมีภาวะแทรกซ้อนอื่น ๆ อีกมากมายที่เกิดจากความคิดนี้และอาจเสี่ยงเกินไปที่จะแก้ไขบางสิ่งพื้นฐานดังนั้นในขณะนี้

คุณสามารถปรับปรุงUsingAsyncเพื่อรองรับการคืนค่าก่อนกำหนด:

public static async Task<R> UsingAsync<T, R>(this T disposable, Func<T, Task<R>> task)
        where T : IAsyncDisposable
{
    bool trySucceeded = false;
    R result;
    try
    {
        result = await task(disposable);
        trySucceeded = true;
    }
    finally
    {
        if (trySucceeded)
            await disposable.DisposeAsync();
        else // must suppress exceptions
            try { await disposable.DisposeAsync(); } catch { }
    }
    return result;
}

ดังนั้นฉันจึงเข้าใจถูกต้อง: ความคิดของคุณคือในบางกรณีawait usingสามารถใช้งานมาตรฐานได้ (นี่คือที่ DisposeAsync จะไม่โยนในกรณีที่ไม่ร้ายแรง) และผู้ช่วยอย่างUsingAsyncเหมาะสมกว่า (ถ้า DisposeAsync มีแนวโน้มที่จะโยน) ? (แน่นอนผมต้องปรับเปลี่ยนUsingAsyncเพื่อให้มันไม่ได้สุ่มสี่สุ่มห้าจับทุกอย่าง แต่ไม่ร้ายแรง (และไม่โง่ในการใช้งานเอริค Lippert ของ ).)
ลาด

@ วลาใช่ - วิธีที่ถูกต้องขึ้นอยู่กับบริบทโดยสิ้นเชิง นอกจากนี้โปรดทราบว่าไม่สามารถเขียน UsingAsync หนึ่งครั้งเพื่อใช้การจัดหมวดหมู่ข้อยกเว้นที่แท้จริงทั่วโลกบางประเภทตามว่าควรจะถูกดักจับหรือไม่ นี่เป็นการตัดสินใจที่จะต้องทำแตกต่างกันไปขึ้นอยู่กับสถานการณ์ เมื่อ Eric Lippert พูดถึงหมวดหมู่เหล่านั้นพวกเขาไม่ใช่ข้อเท็จจริงที่แท้จริงเกี่ยวกับประเภทของข้อยกเว้น ประเภทต่อประเภทข้อยกเว้นขึ้นอยู่กับการออกแบบของคุณ บางครั้งอาจมีการออกแบบ IOException โดยบางครั้งอาจไม่ใช่
Daniel Earwicker

4

บางทีคุณอาจเข้าใจแล้วว่าทำไมสิ่งนี้ถึงเกิดขึ้น แต่มันคุ้มค่าที่จะสะกด await usingลักษณะการทำงานนี้ไม่ได้เฉพาะการ มันจะเกิดขึ้นกับusingบล็อกธรรมดาด้วย ดังนั้นในขณะที่ฉันพูดDispose()ที่นี่ทุกอย่างก็ใช้ได้DisposeAsync()เช่นกัน

usingบล็อกเป็นเพียงน้ำตาลสำหรับการสร้างประโยคtry/ finallyบล็อกเป็นส่วนข้อสังเกตของเอกสารกล่าวว่า สิ่งที่คุณเห็นจะเกิดขึ้นเพราะfinallyบล็อกนั้นจะทำงานอยู่เสมอแม้ว่าจะเกิดข้อยกเว้นก็ตาม ดังนั้นหากมีข้อยกเว้นเกิดขึ้นและไม่มีcatchบล็อกข้อยกเว้นจะถูกพักไว้จนกว่าfinallyบล็อกจะทำงานและจากนั้นจะมีการโยนข้อยกเว้น แต่ถ้ามีข้อยกเว้นเกิดขึ้นfinallyคุณจะไม่เห็นข้อยกเว้นเก่า ๆ

คุณสามารถเห็นสิ่งนี้ด้วยตัวอย่างนี้:

try {
    throw new Exception("Inside try");
} finally {
    throw new Exception("Inside finally");
}

มันไม่สำคัญว่าไม่ว่าจะเป็นDispose()หรือจะเรียกว่าภายในDisposeAsync() finallyพฤติกรรมเหมือนกัน

ความคิดแรกของฉันคือ: อย่าเข้าDispose()มา แต่หลังจากตรวจสอบโค้ดของ Microsoft บางตัวฉันคิดว่ามันขึ้นอยู่กับ

ลองดูที่FileStreamตัวอย่างการนำไปปฏิบัติ ทั้งDispose()วิธีการซิงโครนัสและDisposeAsync()สามารถโยนข้อยกเว้นได้จริง ซิงโครนัสDispose()ไม่สนใจข้อยกเว้นบางอย่างโดยเจตนา แต่ไม่ใช่ทั้งหมด

แต่ฉันคิดว่ามันเป็นสิ่งสำคัญที่จะต้องคำนึงถึงลักษณะของชั้นเรียนของคุณ ในFileStreamตัวอย่างเช่นDispose()จะล้างบัฟเฟอร์ในระบบไฟล์ นั่นคืองานที่สำคัญมากและคุณจำเป็นต้องรู้ว่าล้มเหลว คุณไม่สามารถเพิกเฉยได้

อย่างไรก็ตามในวัตถุประเภทอื่นเมื่อคุณโทรDispose()คุณจะไม่ได้ใช้วัตถุนั้นอีกต่อไป การโทรDispose()จริงๆหมายถึง "วัตถุนี้ตายแล้วสำหรับฉัน" อาจจะล้างหน่วยความจำที่จัดสรรไว้บางส่วน แต่ความล้มเหลวจะไม่ส่งผลต่อการทำงานของแอปพลิเคชันของคุณ แต่อย่างใด Dispose()ในกรณีที่คุณอาจตัดสินใจที่จะไม่สนใจข้อยกเว้นภายในของคุณ

แต่ในกรณีใด ๆ หากคุณต้องการแยกแยะความแตกต่างระหว่างข้อยกเว้นภายในusingหรือข้อยกเว้นที่มาจากDispose()นั้นคุณต้องมีtry/ catchบล็อกทั้งภายในและภายนอกusingบล็อกของคุณ:

try {
    await using (var d = new D())
    {
        try
        {
            throw new ArgumentException("I'm inside using");
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message); // prints I'm inside using
        }
    }
} catch (Exception e) {
    Console.WriteLine(e.Message); // prints I'm inside dispose
}

usingหรือคุณอาจจะไม่ใช้ เขียน a try/ catch/ finallyblock ด้วยตัวคุณเองซึ่งคุณจะได้รับข้อยกเว้นในfinally:

var d = new D();
try
{
    throw new ArgumentException("I'm inside try");
}
catch (Exception e)
{
    Console.WriteLine(e.Message); // prints I'm inside try
}
finally
{
    try
    {
        if (D != null) await D.DisposeAsync();
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message); // prints I'm inside dispose
    }
}

3
Btw, source.dot.net (. NET Core) / Referencesource.microsoft.com (.NET Framework) นั้นง่ายกว่าการเรียกดูมากกว่า GitHub
canton7

ขอบคุณสำหรับคำตอบ! ฉันรู้ว่าเหตุผลที่แท้จริงคืออะไร (ฉันพูดถึงลอง / ในที่สุดและกรณีซิงโครนัสในคำถาม) ตอนนี้เกี่ยวกับข้อเสนอของคุณ catch ภายในusingบล็อกจะไม่ช่วยเพราะมักจะมีการจัดการข้อยกเว้นจะทำที่ไหนสักแห่งไกลจากusingบล็อกตัวเองเพื่อให้การจัดการภายในusingมักจะไม่ได้ในทางปฏิบัติมาก เกี่ยวกับการใช้ไม่using- มันดีกว่าวิธีแก้ปัญหาที่เสนอจริงๆ
วลาด

2
@ canton7 ยอดเยี่ยม! ฉันรับทราบการอ้างอิงแหล่งที่มาของMicrosoft แต่ไม่ทราบว่ามี. NET Core ที่เทียบเท่า ขอบคุณ!
Gabriel Luci

@ วลาด "ดีกว่า" เป็นสิ่งเดียวที่คุณสามารถตอบได้ ฉันรู้ว่าถ้าฉันอ่านรหัสของคนอื่นฉันจะชอบเห็นtry/ catch/ finallyบล็อกเพราะมันจะชัดเจนทันทีว่ามันทำอะไรโดยไม่ต้องไปอ่านสิ่งที่AsyncUsingกำลังทำอยู่ คุณยังคงมีตัวเลือกในการทำผลตอบแทนเร็ว มีนอกจากนี้ยังจะมีค่าใช้จ่าย CPU AwaitUsingพิเศษเพื่อคุณ มันจะเล็ก แต่ก็อยู่ที่นั่น
Gabriel Luci

2
@ PauloMorgado นั่นก็หมายความว่าDispose()ไม่ควรโยนเพราะมันถูกเรียกมากกว่าหนึ่งครั้ง การใช้งานของ Microsoft นั้นอาจมีข้อยกเว้นและด้วยเหตุผลที่ดีดังที่ฉันได้แสดงไว้ในคำตอบนี้ อย่างไรก็ตามฉันยอมรับว่าคุณควรหลีกเลี่ยงหากเป็นไปได้เนื่องจากไม่มีใครคาดว่ามันจะโยน
Gabriel Luci

4

ใช้อย่างมีประสิทธิภาพยกเว้นรหัสการจัดการ (ไวยากรณ์น้ำตาลสำหรับลอง ... ในที่สุด ... ทิ้ง ()

หากรหัสการจัดการข้อยกเว้นของคุณกำลังขว้างข้อยกเว้นบางอย่างจะถูกเก็บไว้อย่างมากมาย

อะไรก็ตามที่เกิดขึ้นกับคุณแม้กระทั่งนำคุณไปที่นั่นไม่ได้เป็นแม่อีกต่อไป รหัสการจัดการข้อผิดพลาดจะซ่อนข้อยกเว้นที่เป็นไปได้ทั้งหมดไม่ทางใดก็ทางหนึ่ง รหัสการจัดการข้อยกเว้นต้องได้รับการแก้ไขซึ่งมีความสำคัญสูงสุด หากปราศจากสิ่งนั้นคุณจะไม่ได้รับข้อมูลการดีบักเพียงพอสำหรับปัญหาจริง ฉันเห็นว่ามันทำผิดอย่างมากบ่อยครั้ง มันเป็นเรื่องง่ายที่จะเข้าใจผิดว่าจัดการตัวชี้เปล่า บ่อยครั้งที่มีบทความสองบทความเกี่ยวกับหัวข้อที่ฉันเชื่อมโยงซึ่งอาจช่วยให้คุณเข้าใจถึงความเข้าใจผิดเกี่ยวกับการออกแบบ:

นี่คือสิ่งที่คุณต้องทำหากการจัดการข้อยกเว้น / รหัส Dipose ของคุณขึ้นอยู่กับการจำแนกประเภทข้อยกเว้น

สำหรับ Fatal, Boneheaded และ Vexing วิธีการแก้ไขก็เหมือนกัน

ข้อยกเว้นภายนอกจะต้องหลีกเลี่ยงแม้จะมีค่าใช้จ่ายสูง มีเหตุผลที่เรายังคงใช้ล็อกไฟล์แทนจากนั้นล็อกฐานข้อมูลเพื่อบันทึกข้อยกเว้น - DB Opeartions เป็นวิธีที่จะทำให้เกิดปัญหาจากภายนอกได้ง่าย Logfiles เป็นกรณีหนึ่งที่ฉันไม่สนใจถ้าคุณเก็บ File Handle เปิด Runtime ทั้งหมด

หากคุณต้องปิดการเชื่อมต่อไม่ต้องกังวลกับส่วนอื่น ๆ จัดการได้เหมือนกับที่ UDP ทำ: "ฉันจะส่งข้อมูล แต่ฉันไม่สนใจว่าอีกฝ่ายรับข้อมูลมาหรือไม่" การกำจัดคือการล้างทรัพยากรในฝั่งไคลเอ็นต์ / ฝั่งที่คุณกำลังทำงาน

ฉันสามารถลองแจ้งเตือนได้ แต่การล้างข้อมูลในฝั่งเซิร์ฟเวอร์ / FS หรือไม่ นั่นคือสิ่งที่พวกเขาหมดเวลาและพวกเขาจัดการข้อยกเว้นเป็นผู้รับผิดชอบ


ดังนั้นข้อเสนอของคุณจึงลดลงอย่างมีประสิทธิภาพเพื่อระงับข้อยกเว้นเมื่อปิดการเชื่อมต่อใช่ไหม
ลาด

@ วลัดคนภายนอก? แน่ใจ Dipose / Finalizer อยู่ที่นั่นเพื่อทำความสะอาดตัวเอง โอกาสที่จะเกิดขึ้นเมื่อปิด Conneciton อินสแตนซ์เนื่องจากข้อยกเว้นคุณทำเช่นนั้นเพราะคุณไม่มีการเชื่อมต่อที่ใช้งานได้อีกต่อไป และจะมีจุดใดในการรับข้อยกเว้น "ไม่เชื่อมต่อ" ในขณะที่จัดการข้อยกเว้น "ไม่มีการเชื่อมต่อ" ก่อนหน้า คุณส่ง "โย่ฉันปิดการเชื่อมต่อนี้" ซิงเกิ้ลที่คุณไม่สนใจข้อยกเว้นภายนอกทั้งหมดหรือแม้ว่าจะเข้าใกล้เป้าหมาย Afaik การใช้งานเริ่มต้นของการกำจัดแล้วทำอย่างนั้น
Christopher

@ วลาด: ฉันจำได้ว่ามีหลายสิ่งหลายอย่างที่คุณไม่ควรจะโยนออกไป (ยกเว้นพวก Coruse Fatal) ประเภท Initliaizers อยู่ในรายการ การกำจัดยังเป็นหนึ่งในนั้น: "เพื่อช่วยให้แน่ใจว่าทรัพยากรได้รับการทำความสะอาดอย่างเหมาะสมเสมอวิธีการกำจัดควรเรียกได้หลายครั้งโดยไม่ทิ้งข้อยกเว้น" docs.microsoft.com/en-us/dotnet/standard/garbage-collection/…
Christopher

@ วลาดโอกาสของข้อยกเว้นร้ายแรงหรือไม่ เรามักจะต้องเสี่ยงเหล่านั้นและไม่ควรจัดการกับพวกเขาเกินกว่า "โทรทิ้ง" และไม่ควรทำอะไรกับพวกนั้น ในความเป็นจริงพวกเขาไปโดยไม่พูดถึงในเอกสารใด ๆ | ข้อยกเว้นที่ถูกหลอกลวง แก้ไขให้เสมอ | ข้อยกเว้น Vexing เป็นตัวเลือกที่สำคัญสำหรับการกลืน / การจัดการเช่นใน TryParse () | ภายนอก? นอกจากนี้ควรได้รับการ handeled บ่อยครั้งที่คุณต้องการบอกผู้ใช้เกี่ยวกับพวกเขาและบันทึกพวกเขา แต่ไม่อย่างนั้นพวกเขาก็ไม่คุ้มค่าที่จะฆ่ากระบวนการของคุณ
Christopher

@ วลาดฉันค้นหา SqlConnection.Dispose () มันไม่สนใจที่จะส่งอะไรไปยังเซิร์ฟเวอร์เกี่ยวกับการเชื่อมต่อ บางสิ่งบางอย่างอาจยังคงเกิดขึ้นเป็นผลมาจากการและNativeMethods.UnmapViewOfFile(); NativeMethods.CloseHandle()แต่สิ่งเหล่านั้นนำเข้ามาจากภายนอก ไม่มีการตรวจสอบค่าตอบแทนใด ๆ หรือสิ่งอื่นใดที่สามารถใช้เพื่อรับข้อยกเว้น. NET รอบ ๆ สิ่งที่ทั้งสองอาจประสบ ดังนั้นฉันจึงมั่นใจอย่างยิ่ง SqlConnection.Dispose (bool) ก็ไม่สนใจ | การปิดนั้นดีกว่ามากบอกกับเซิร์ฟเวอร์ ก่อนที่จะสายการกำจัด
Christopher

1

คุณสามารถลองใช้ AggregateException และแก้ไขโค้ดของคุณดังนี้:

class Program 
{
    static async Task Main()
    {
        try
        {
            await using (var d = new D())
            {
                throw new ArgumentException("I'm inside using");
            }
        }
        catch (AggregateException ex)
        {
            ex.Handle(inner =>
            {
                if (inner is Exception)
                {
                    Console.WriteLine(e.Message);
                }
            });
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
}

class D : IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        await Task.Delay(1);
        throw new Exception("I'm inside dispose");
    }
}

https://docs.microsoft.com/ru-ru/dotnet/api/system.aggregateexception?view=netframework-4.8

https://docs.microsoft.com/ru-ru/dotnet/standard/parallel-programming/exception-handling-task-parallel-library

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.