web-dev-qa-db-ja.com

Utf8JsonReaderおよびReadOnlySequenceに非同期関数がない理由

私はUtf8Jsonをたくさん使用しましたが(それは非常に良いことです)、それ以降、いくつかの下位レベルのコードを適合させ、Utf8JsonReaderを直接使用し始めました。

Utf8Jsonライブラリのコードを見ると、最終的にはJsonSerializer.DeserializeAsync関数を含むSystem.IO Streamクラスを使用しているため、Stream.ReadAsyncが表示されます。

Utf8JsonReaderを見ると、ReadOnlySequence<byteを使用しており、非同期関数はありません。

ここを見る https://github.com/dotnet/runtime/issues/29906 言及:

Utf8JsonReaderは再入可能であるため、追加の状態を含むUtf8JsonReaderの非同期ラッパーは、必要なタイプを作成してストリーミング読み取りを実行できる完全同期ヘルパー関数にシェルアウトできます。

私の質問は喜んでです(そして読む時間をありがとう):

  1. なぜ1つのライブラリがメモリバッファーに非同期で(Utf8Json)、別のライブラリに同期的に(Utf8JsonReader)アクセスするのでしょうか。

  2. 非同期は、ネットワークポートやディスク上のファイルなど、長期にわたるIOには意味があると理解していますが、メモリ上ではオーバーヘッドが有害になるでしょうか? (これがReadOnlySequence<byte>に非同期機能がない理由ですか?)

  3. Githubサイトの作者は、非同期ラッパーを作成することによって何を意味し、これはどのように見えますか?

1
morleyc

私はGitHubの問題から引用された「op」です。 _System.Text.Json_ APIが最初にプレビューされたとき、非同期にストリームを消費する関数がなかったため、その機能を追加するために、その周りに独自のラッパーを記述する必要がありました。

asyncメソッドはoutメソッドであるため、Spanパラメータやスタック割り当て変数(asyncおよびco)など、スタックに依存する多くの機能を使用できません。割り当てとスコープの終わりの間のある時点で譲歩する可能性があります。ただし、asyncメソッドは、これらの機能を自由に使用できる通常の非asyncメソッドを自由に呼び出すことができます。これは、スタックの実行中、スタックがその場に留まることが保証されているためです。ヘルパー関数。

私のニーズのために、 最初にStreamSequence を作成しましたStreamを非同期で消費することを許可しましたが、単一のReadOnlySequenceSegmentにまとめることができるReadOnlySequenceとして一度にチャンクをロードすることにより、高性能な方法で同期のみの_Utf8JsonReader_にコンテンツをフィードします。理論的には、JSONが消費されると、以前のReadOnlySequenceインスタンスを削除することで、ReadOnlySequenceSegmentの先頭でメモリページを再利用できますが、これは実装されていません。

次に、これを、ストリームを解析するステートマシンで使用し、asyncを使用するコアStreamSequence AP​​I関数を使用して、一度にストリームからJSONコンテンツのチャンクをロードし、 _ReadOnlySequence<byte>_の内容をJSONとして解析し、必要な型にシリアル化する責任がある専用(プライベート)非async関数に渡します。 これは実装から取り除かれたコードです 、人生を邪魔したので、それをより一般化したりクリーンアップしたりすることはありませんでした。 (私はこの返信のためにそれを行うことを考えましたが、投稿に取り掛かるまでに1〜2か月かかる可能性があるので、これはおそらくより良いでしょう。)

これはすべてSTJプレビュー6に基づいています。APIはpreview7で現在の状態にわずかに変更されました。おそらく部分的に、公開された内部状態を見つけたときの混乱について、当時提出したフィードバックが原因です( #29906 =と #29911 )ですが、概念は同じです。 STJのpreview6バージョンをインストールして、このコードをいじってみて、それが正常に機能したら、最新リリースにアップグレードして破損を修正することもできます。すべての場合において、非同期エントリポイントから作業をシェルアウトするための同期ヘルパーメソッドを作成する方法と、その同期コンテキストでSpanおよびcoを自由に使用する方法が明らかであると思います。

現在のバージョンのSTJ APIが実際に_DeserializeAsync<T>_メソッドを提供していることに注意してください。これは、実際にすべてのコードを置き換えただけです。これは、カスタムの逆シリアル化の必要性が利点を上回らなかったためです(非同期のSTJメソッドはありませんでした)時間は私が書いた時間ですが、JSONストリームに手動で「飛び込み」、_Utf8JsonReader_と直接やり取りする必要がある場合は、somethingこれらの線に沿って。

(私のStreamSequenceの代わりに同梱されている可能性があることに注意してください。たぶん、急速に変化するASP.NET Core APIに詳しい人がそれにコメントできるかもしれません。)


使用法の更新:StreamSequenceインスタンスの周りにStreamを作成します。これは、それらを結び付ける以外何もしません。 StreamSequence.ReadMoreAsync()は、スレッドプールをブロックせずに、基になるストリームから非同期にバイトを消費します。プロパティ_StreamSequence.Sequence_は、これまでのストリームの内容を表す_ReadOnlySequence<byte>_を公開します。 ReadMoreAsync()への後続の各呼び出しは、_ReadOnlySequence<byte>_を別のReadOnlySequenceSegmentで拡張し、より多くのデータを利用できるようにします。 StreamSequenceをインスタンス化し、一度読み取り、次に_StreamSequence.Sequence_をパーサーヘルパーに渡します。パーサーが操作を続行するためにより多くのデータを必要とする場合は、それを知らせるフラグを返して、非同期エントリポイントにバブルアップし、自由にawait ReadMoreAsync()を呼び出してから、パーサーを再度呼び出してジョブを続行します。 (必要に応じて状態情報を渡します)。

3