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 DisposeAsync
né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à AsyncDispose
ngoại lệ nếu nó bị ném và ngoại lệ chỉ từ bên trong await using
nếu AsyncDispose
không ném.
Tuy nhiên, tôi thích nó theo cách khác: lấy ngoại lệ từ await using
khối nếu có thể và DisposeAsync
chỉ ngoại lệ khi await using
khối kết thúc thành công.
Đặt vấn đề: Tưởng tượng rằng lớp của tôi D
hoạ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 using
có 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ệ DisposeAsync
là 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 DisposeAsync
khô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ệ finally
ghi đè 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 using
nế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.
CloseAsync
phươ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.
Dispose
luô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 AsyncDispose
nên khác đi.
DisposeAsync
gắ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.
Close
phương thức riêng vì lý do này. Có lẽ nên làm điều tương tự:CloseAsync
cố gắng đóng mọi thứ lại một cách độc đáo và ném vào thất bại.DisposeAsync
chỉ làm tốt nhất của nó, và thất bại âm thầm.