本サイトからスピンオフした姉妹サイトです。C#2016年8月31日C#の進化の中でも「非同期メソッド」はコーディング方法を大きく変えるほど革新的だったが、そこにはまだ課題もある。7~将来のC#で、非同期処理はどう進化するのか、前後編で見ていこう。11で追加された非同期メソッドは、プログラミング作業に大きなインパクトを与えた革新的な機能である。C#開発での非同期処理は劇的に楽に書けるようになった。C#の非同期メソッドを参考にして、ECMAScriptやC++にも同様の構文が提案されている。要望はあるのに現状の非同期メソッドではできないことも残されている。大きなものは以下の2つである。5.0の直後からずっと要望が挙がっているものである。それが未解決なのはそれなりに難しい課題を抱えているからだ。今回は、これらの課題とその解決策について説明していく。前編では(1)非同期メソッドの戻り値の問題について、後編では(2)非同期シーケンスについて話していこう。非同期メソッドの戻り値(Task-like)現在の非同期メソッドでは、戻り値の型はTask、Task<T>(いずれもSystem.Threading.Tasks名前空間)、もしくはvoidでなければならい。この仕様によって、非同期メソッドは、Taskクラスという特定の型に依存している状態である。これに対して、「Taskクラスに近い条件を持った型であれば、任意の型を戻り値にできるようにしたい」という案が出ている。このTaskクラスに近い条件を持った型を指して、「Task-like」(Task風)と呼んでいる。一般論でいうと、言語機能が特定のクラスに依存するのはリスクでしかない。そのクラス(今回の場合、Taskクラス)が時代遅れになったときに、言語機能(同、非同期メソッド)も一緒に時代遅れになりかねないからだ。そこで通常は、「所定のメソッドさえ実装していれば具体的な型は問わない」というような、型を明示しない仕様にするものである。実際、C#でも、foreachステートメントやクエリ式などはそのような仕様になっている(GetEnumeratorなどのメソッドだけ持っていれば何でもよく、C#コンパイラーが特定の型に依存することはない)。課題を許容した理由C#チームは最初からこの機能に一定の需要があることを把握していた。5.0のときはトレードオフの検討の結果、Taskクラスへの依存という妥協を選択した。その課題を要約したコードをリスト1に示そう。リスト1では、Taskクラス以外に、MyTask1、MyTask2というクラスも非同期メソッドの戻り値にできるものと仮定する。課題というのは大まかにいうと、「どこまでTaskクラスと似ていれば、それをTask-likeといえるのか」と「オーバーロード解決が型推論で候補が複数ある場合にどれを選択するか」である。リスト1中のコメントに番号を振っているが、以下のような検討項目がある。リスト1中の3のように、型推論の候補が複数(この場合はTask<int>型とMyTask1<int>型)ある場合、どちらが選ばれるべきかこのうち解決が簡単なのは「結果のプロパティ」くらいで、残りはなかなかの難問である。型推論がうまくいかないようでは、非同期メソッドの利便性が大きく損なわれることになる。一方で、Task/Task<T>クラス以外の型を使いたいことがどのくらいあるのかという話になる。builder」と呼ばれるそれなりに複雑なコード(後述)を書く必要がある。して使いたい機能かというと、当時、あまり需要がないと想像された。5.0では戻り値の型をTaskクラスに制限する妥協案を選択した。需要が低いものに時間を割いて、リリースが遅れてはいけない。perfect」という言葉もあるように、必要なタイミングで必要な機能を提供できることが大切である。8のリリースとタイミングをそろえる必要もあった。ValueTask非同期メソッドの戻り値に対する制限はトレードオフの選択結果であり、意図して行った妥協なので、よほどのメリットがない限り緩和する意味はない。そのよほどのメリットが見つかってしまった。前々回説明したValueTask構造体である。前々回の説明の通り、ValueTask構造体はパフォーマンス向上のために追加される構造体である。補足として、大幅なパフォーマンス改善が見込めるケースを1つ紹介しておこう。リスト2のような例を考える。asyncは連鎖する。非同期処理の連鎖とキャッシュ本当に非同期処理が必要になるのは最初の1回きりで、以降はキャッシュを使いまわせる状況である。この例は単純化したものだが、似たような処理が必要になることは多いだろう。ここで問題となるのは、非同期メソッドの連鎖だ。細かくメソッドを分割して、それらの呼び出しが階層的になる場合、1カ所でも非同期メソッドがあるなら呼び出し階層に含まれる全てのメソッドが非同期メソッドになる。階層の全てでTaskクラスのインスタンスが作られるとそれなりの負担が発生する。最初の1回は本当にTaskクラスが必要とされるが、キャッシュが効いている状況でまでインスタンスが作られるのは完全に無駄である。これが全てValueTask構造体に置き換わるとかなりのパフォーマンス改善につながるだろう。実際、corefxチームやASP.NETチームなどによって、構造体への置き換えが10倍近い高速化につながったケースも報告されているそうだ。いえ、これらの高速化は、非同期メソッドを使わず、手作業で最適化を行ったものである。マイクロソフト内であっても、多くのチームが「手作業で最適化はしない。非同期メソッドが対応してくれれば同種の高速化作業をする」という態度を示しているらしい。そこで、「非同期メソッドのValueTask構造体への対応」が高い優先度を持つこととなった。いえ、ここでまたValueTask構造体だけを特別扱いする必要はないだろう。この際、任意のTask-likeを認めることについてあらためて検討すべきである。前述の通り、非同期メソッドの戻り値をTaskクラスかTask<T>クラスに限定するに至った課題があった。任意のTask-likeを認めるに当たって、これらの課題に対する再検討が必要になる。7では以下のような方針になるようだ。互換性を維持するため、既存のTask<T>型を優先する。builderTask-likeと認められる(非同期メソッドの戻り値として使える)ためには、リスト3に示すようなメソッドを持つ必要がある。これらは同じ名前/同じ引数リストであれば何でもよく、インターフェースの実装などは不要である。C#コンパイラーが特定の型に依存することはない。builderと呼ばれる別の型(同、MyTaskMethodBuilderクラス)を探す機能だけである。builder側のメソッドがどう使われるかは本稿の範(はん)疇(ちゅう)を超えるので省略する。builderを探す仕組みに関しては、以下のような複数の案が検討された。Task-likeに属性を付ける(リスト3はこの方式)インスタンスメソッド/拡張メソッド使う静的メソッドを使う1の属性を付ける方法の場合、既存の型への拡張ができないという問題がある。builderを追加することができない。このAsyncBuilder属性をどこで定義するかという問題がある。7と同時に.NETの標準ライブラリに入るだろうが、それよりも古いバージョンの.NET向けのプログラムでも使えるように、この属性を配布する手段が必要になる。その点、2の方式であれば、拡張メソッドを使えば既存の型の拡張もできる。builderのインスタンスが必要で、素直に拡張メソッドを呼ぶ手段がない。3の静的メソッドを使う方法では、属性の場合と同じく拡張性に問題がある。Task-likeの話とは別に、C#に静的拡張メソッドのようなものを追加する案も出ていて、これが入れば拡張性の問題は解決する。最終的に1の方式が採用されそうなのだが、その理由としては以下の通りだ。既存の型に対する拡張ができないという問題に対しては、そもそもその需要は少ないないだろうことと、Task-like側に属性を付ける以外に非同期メソッド側に属性を付ける手法も認めることで問題が緩和するあてがあることがある。AsyncBuilder属性の配布方法については、(名前空間まで含めて)同名の属性であれば、どこで定義されたものでもよく、internalであってもよいことにする。Task-like型と同じライブラリ内に自前で属性を用意できるようにして、そもそも配布の必要性をなくすことで対処する。将来の話をするなら静的拡張メソッドと同様に拡張属性もあり得るだろう。ちなみに、どの方式でも、属性の有無やメソッドの有無だけでオーバーロード解決の仕方が変わるという、C#の他の機能には見られない珍しい挙動をしてしまうという問題はある。この問題に対して、C#チームは当初強い懸念を抱いていたが、どの方式でも結局珍しい挙動になること、利用頻度が極端に低い(=Task-like型を作る側だけの問題。Task-like型が作られる機会はめったにない)機能に対してこだわるべきでないということから、妥協を選択することにした。主役はValueTask構造体だとはいえ、せっかく任意のTask-likeを使えるようになるわけである。他の用途もいろいろと考えらえる。RuntimeのIAsyncOperation<TResult>インターフェース(Windows.Foundation名前空間)など、Taskクラス以外を使う非同期ライブラリとの相互運用に使える。現状では、リスト4のように、1段階の変換処理が必要になる。Extensionsとの連携で、IObservable<T>インターフェースを返す非同期メソッドなども考えられる。戻り値の型がクラスではなくインターフェースであれば、型引数を共変にできる。リスト5に示すように、Task<T>クラスのインターフェ-ス版ITask<T>があれば、ITask<string>をITask<object>に代入できる。builderも作れるだろう。かなりの乱用・悪用ではあるが、非同期処理以外のこともできる。null許容型に対して、null条件演算子と似たような処理を実現する例も挙がっている。Tasklike(英語)未解決の課題・新たな課題:言語機能がTaskクラスに依存しているという問題は解消しそうだが、未解決の課題や新たな課題も残されている。builderを探すのに属性を使うことになる。その結果、現状では、他人が書いた型をTask-likeにできない(他人が書いた型には属性を付けられない)という課題が残されている。この課題の解決には何らかの新しい文法が必要になる。メソッドだけでなくなんでも拡張する機能が提案されてはいるが、既存の型に属性を追加するのは特に難しく、今のところ提案に挙がっていない。Extensionsの一環としてIObservable<T>をTask-like化することが難しくなる。言語レベルでのobsoleteサポート非同期メソッドの戻り値をTaskクラスからValueTask構造体に変更することで、場合によっては10倍近いパフォーマンス向上につながることがある。自分が作っていたライブラリがまさにその場合に該当するとしたらどうするだろうか。今すぐにでもValueTask化したいと思うのも自然だろう。ライブラリの互換性を考えると単純に戻り値を変更するわけにはいかない。オーバーロードを追加したいところである。そこで問題になるのが、リスト6に示すような、戻り値違いのオーバーロードやラムダ式の型推論である。一般論で言うと、この問題の解決策はない。戻り値違いのオーバーロードもラムダ式の型推論の解決もできない。一方で、今回の場合に限って言うなら、本当にやりたいことは置き換えであって、古いメソッドを残す理由は互換性の維持である。古い方の優先度を下げるような仕組みがあれば問題を解決できるかもしれない。そこで検討に挙がっているのが、言語レベルでのobsolete(廃止機能)サポートである。リスト7に示すように、古い方のメソッドにobsoleteキーワードを付けることで、そちらのオーバーロードが選ばれないようにしたいというものだ。このような特殊なオーバーロード解決は、既存のObsolete属性(System名前空間)を使った注釈では実現できない。7では、非同期メソッドの戻り値として任意の「Task-like」(Task風の型)が使えるようになる予定である。主な動機としてはValueTask構造体を用いることによるパフォーマンス改善となる。5の時点でも検討はされていたが、課題もあり、最終的にはTaskクラスに限られることになっていた。7での拡張が再検討された。Task-likeを認めることで新たな課題も出ている。それらの課題に対して、さらに、言語的なobsoleteサポートや、既存の型の拡張などが検討されている。1.0がプレビュー版だった頃からC#によるプログラミング入門を公開していて、C#とともに今年で15年になる。最近の自己紹介は「C#でググれ」。※以下では、本稿の前後を合わせて5回分(第7回~第11回)のみ表示しています。INDEX]を参照してください。前編に続き、次期C#のパフォーマンス向上について解説。7以降での採用が予定もしくは検討されているパフォーマンス向上関連の新機能の内容を具体的に見ていこう。7にはどんな新機能が含まれるのかが見えてきた。7はかなり速いペースでのリリースとなる。その背景にはどんな事情があるのだろうか。Task-likeC#の進化の中でも「非同期メソッド」はコーディング方法を大きく変えるほど革新的だったが、そこにはまだ課題もある。7~将来のC#で、非同期処理はどう進化するのか、前後編で見ていこう。Basic)が切り開いた非同期処理の新たな世界。そこにはまだ課題もある。7でどうなるかを見てみよう。null参照問題(前編)Cの系譜を継ぐC#ではnullが長らく使い続けられてきたが、最近ではその存在が大きな問題だと認識されている。前後編でこの問題を取り上げ、今回(前編)はnullを取り巻く事情について考察する。機械学習エンジニアを目指す人~初中級者のための技術情報サイトです。「機械学習&AI」「クラウド&ビッグデータ」「IoTデバイス」のネット記事情報を共有中。開発者のためのSlackチーム、作りました:「機械学習&AI」「クラウド&ビッグデータ」「IoTデバイス」のネット記事情報を共有中。2020最適なビューへの切り替えを推奨します:スマートフォン用表示に。