新しい.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
それがスローされた場合の-exceptionと、スローされawait using
なかった場合にのみ内部からの例外AsyncDispose
です。
ただし、逆の方法を使用await using
することをおDisposeAsync
勧めawait using
します。可能であればブロックから例外を取得し、ブロックが正常に終了した場合にのみ例外を取得します。
理論的根拠:クラスD
がいくつかのネットワークリソースで動作し、いくつかの通知をリモートでサブスクライブすることを想像してください。内部のコードawait using
は何か問題を起こして通信チャネルに失敗する可能性があります。その後、Disposeの通信を適切に閉じようとする(たとえば、通知の購読を解除する)コードも失敗します。ただし、最初の例外は問題に関する実際の情報を提供し、2番目の例外は二次的な問題にすぎません。
もう1つのケースでは、メインパートが実行され、破棄が失敗した場合、本当の問題は内部DisposeAsync
にあるため、からの例外DisposeAsync
は関連するものです。これは、内部のすべての例外を抑制するだけDisposeAsync
では良い考えではないことを意味します。
非非同期の場合にも同じ問題があることを知ってい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");
});
これは一種の醜いものです(また、usingブロック内での早期復帰などは許可されていません)。
await using
可能であれば、良い標準的な解決策はありますか?インターネットでの検索では、この問題についての議論すら見つかりませんでした。
CloseAsync
それを実行するには特別な注意を払う必要があるという別の手段がある。using
-block の最後に置くだけの場合、初期のリターンなど(これは私たちが起こしたいことです)と例外(これは私たちが起こしたいことです)ではスキップされます。しかし、アイデアは有望に見えます。
Dispose
常に「物事がうまくいかなかった可能性があります。状況を改善するために最善を尽くしますが、悪化させないでください」であり、なぜAsyncDispose
違いがあるのかわかりません。
DisposeAsync
片付けには最善を尽くすが、投げないようにすることは正しいことです。あなたは意図的なアーリーリターンについて話していましたが、意図的なアーリーリターンは誤ってへの呼び出しをバイパスする可能性CloseAsync
があります。これらは多くのコーディング標準で禁止されているものです。
Close
まさにそのために別のメソッドがあると思います。おそらく同じことをするのが賢明CloseAsync
です。物事をうまく閉じ込めようとし、失敗した場合はスローします。DisposeAsync
最善を尽くし、静かに失敗します。