Trong quá trình chuyển sang .NET Core 3 mới IAsynsDisposable, tôi đã gặp phải vấn đề sau.
Cốt lõi của vấn đề: nếu DisposeAsyncném ngoại lệ, ngoại lệ này sẽ ẩn bất kỳ ngoại lệ nào được ném vào bên trong 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");
}
}
Điều bị bắt là AsyncDisposengoại lệ nếu nó bị ném và ngoại lệ chỉ từ bên trong await usingnếu AsyncDisposekhông ném.
Tuy nhiên, tôi thích nó theo cách khác: lấy ngoại lệ từ await usingkhối nếu có thể và DisposeAsyncchỉ ngoại lệ khi await usingkhối kết thúc thành công.
Đặt vấn đề: Tưởng tượng rằng lớp của tôi Dhoạt động với một số tài nguyên mạng và đăng ký một số thông báo từ xa. Mã bên trong await usingcó thể làm điều gì đó sai và làm hỏng kênh liên lạc, sau đó mã trong Dispose cố gắng đóng giao tiếp một cách duyên dáng (ví dụ: hủy đăng ký thông báo) cũng sẽ thất bại. Nhưng ngoại lệ đầu tiên cho tôi thông tin thực sự về vấn đề này, và ngoại lệ thứ hai chỉ là vấn đề thứ yếu.
Trong trường hợp khác khi phần chính chạy qua và xử lý không thành công, vấn đề thực sự nằm ở bên trong DisposeAsync, do đó, ngoại lệ DisposeAsynclà phần có liên quan. Điều này có nghĩa là chỉ cần loại bỏ tất cả các ngoại lệ bên trong DisposeAsynckhông nên là một ý tưởng tốt.
Tôi biết rằng có một vấn đề tương tự với trường hợp không đồng bộ: ngoại lệ finallyghi đè lên ngoại lệ try, đó là lý do tại sao không nên đưa vào Dispose(). Nhưng với các lớp truy cập mạng loại bỏ các ngoại lệ trong các phương thức đóng không hoàn toàn tốt.
Có thể giải quyết vấn đề với người trợ giúp sau:
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 { }
}
}
}
và sử dụng nó như
await new D().UsingAsync(d =>
{
throw new ArgumentException("I'm inside using");
});
đó là loại xấu xí (và không cho phép những thứ như trả lại sớm bên trong khối sử dụng).
Có một giải pháp tốt, hợp quy, await usingnếu có thể? Tìm kiếm của tôi trên internet thậm chí không tìm thấy thảo luận về vấn đề này.
CloseAsyncphương tiện riêng mà tôi cần phải thực hiện các biện pháp phòng ngừa bổ sung để chạy nó. Nếu tôi chỉ đặt nó ở cuối using-block, nó sẽ bị bỏ qua khi trả về sớm, v.v. (đây là điều chúng tôi muốn xảy ra) và ngoại lệ (đây là điều chúng tôi muốn xảy ra). Nhưng ý tưởng có vẻ đầy hứa hẹn.
Disposeluôn luôn là "Mọi thứ có thể đã sai: chỉ cần cố gắng hết sức để cải thiện tình hình, nhưng đừng làm nó tồi tệ hơn", và tôi không hiểu tại sao AsyncDisposenên khác đi.
DisposeAsyncgắng hết sức để dọn dẹp nhưng không ném là điều nên làm. Bạn đang nói về việc trả lại sớm có chủ ý , trong đó việc trả lại sớm có chủ ý có thể bỏ qua một cuộc gọi đến CloseAsync: đó là những điều bị cấm bởi nhiều tiêu chuẩn mã hóa.
Closephương thức riêng vì lý do này. Có lẽ nên làm điều tương tự:CloseAsynccố gắng đóng mọi thứ lại một cách độc đáo và ném vào thất bại.DisposeAsyncchỉ làm tốt nhất của nó, và thất bại âm thầm.