xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

gRPC / MagicOnion 入門 (2) - 4 種類の通信方式

gRPC は HTTP/2 の仕様に則った形で 4 種類の通信方式を提供しています。以下のドキュメントにも解説がありますが、簡単に図解します。

f:id:xin9le:20170604212009p:plain

Unary RPCs

最もシンプルな 1 リクエスト / 1 レスポンス型の通信方法です。通常の関数コールのように扱えるため、非常に分かりやすいのが特徴です。

f:id:xin9le:20170604153513p:plain

Server Streaming RPCs

クライアントが 1 度リクエストを送信し、サーバーから複数回数のレスポンスを返す方式です。クライアントはサーバーから送信完了の信号が来るまでストリームからメッセージを読み続けます。いわゆるサーバープッシュ型の実装に利用できます。

f:id:xin9le:20170604153542p:plain

Client Streaming RPCs

クライアントから複数回数のリクエストを送信し、それらを読み込んだサーバー側から 1 度のレスポンスを待つ方式です。サーバーはクライアントからリクエストの送信完了信号が来るまでストリームからメッセージを読み続け、レスポンスを返しません。

f:id:xin9le:20170604153550p:plain

Duplex Streaming RPCs

双方向ストリーミングの通信方式です。2 つのストリームはそれぞれ独立しているため、クライアントとサーバーはどのような順序でも読み書きが可能です。リクエストとレスポンスが 1 対 1 である必要がなく、扱い/実装共に難易度が高いのが特徴です。各ストリームのメッセージ送信順序は保証されます。

f:id:xin9le:20170604153558p:plain

ドキュメントには「Bidirectional Streaming」と記載されている場合がありますが、gRPC のコード上では「Duplex Streaming」とされているので、この連載では「Duplex Streaming」を使って解説を進めます。

gRPC / MagicOnion 入門 (1) - 概要

gRPC は Google が開発している HTTP/2 ベースの RPC 通信フレームワークで、以下のような特徴を備えています。

  • Protocol Buffers を利用したサービス定義
  • 多数のプラットフォーム/言語をサポート
  • HTTP/2 ベースの高パフォーマンスなストリーミング通信
  • 統合されたプラガブルな認証機構

gRPC は GitHub にて今も積極的に開発が進められており、Google も自身の Google Cloud Platform の API などとして積極的に採用しています。

f:id:xin9le:20170603185008p:plain

これまで HTTP/1 ベースでのストリーミング通信と言えば WebSocket などが有名ですが、通常の HTTP リクエストとは API が統合されておらず、サービスの口を 2 系統用意する必要がありました。ASP.NET に限って言えば、同様のコンセプトの SignalR がありますが、gRPC の方が高速で拡張性があり、先進的であると言えます。

MagicOnion

@neuecc さんが開発している、.NET 用の gRPC を薄くラップした高レベルな通信フレームワークです。素の gRPC の良さをそのまま残しつつ、C#er がより便利に利用できるように調整されています。

  • .NET Framework / .NET Core / Unity に対応
    • Unity 向けの gRPC Port を含み、それを利用して Unity で動作させる
  • MessagePack for C# を用いた超高速シリアライズ
  • Protocol Buffers を使わないため .proto (IDL : 中間定義) が一切不要
  • Heartbeat によるサーバー/クライアントでの切断検知
  • OWIN / ASP.NET Core MVC ライクな Filter による制御
  • Swagger を用いた HTTP/1 との相互運用

MagicOnion はグラニが開発/運用している黒騎士と白の魔王Project Sonata で採用されています。ただし、もし実プロダクトとしての採用を検討する場合は十分な検証を行ってください。

今すぐ試してみたいという方は、Quick Start が MagicOnionREADME.md に記載されているので是非トライしてみてください。

gRPC / MagicOnion 入門

HTTP/2 をベースとした通信フレームワーク gRPC と、その高レベルラッパーである MagicOnion についての連載インデックス

インデックス

f:id:xin9le:20170603134144p:plain

値の破棄

1 年ほど前 C# 7.0 の新規のについていろいろ書いていたのですが、値の破棄 (discards) という機能について書いてなかったことに気が付きました!ということで、イマサラですが紹介します。

これまでの「値を使わない」ときの書き方

コンパイルを通すために引数を指定しなければならないけれど、その引数は今使いません。

…と言ったケースはままあります。そんな「値を無視したい」ときによく見かける実装が「_ (アンダースコア)」変数によるエスケープです。慣例レベルではありますが、「利用しない変数」であることを明示した書き方をします。例えば以下のような感じです。

int _;
if (int.TryParse("123", out _))
{}

しかし C# においてアンダースコアは変数名として有効なので、複数の値を無視したい場合は以下のようにしなければなりません。

//--- 使いもしない変数なのにドンドン _ の数が増えていく...!
int _;
if (int.TryParse("123", out _)){}

int __;
if (int.TryParse("456", out __)){}

int ___;
if (int.TryParse("789", out ___)){}

//--- 当然変数として有効なので、ここで触ることができる
___ += 10;
Console.WriteLine(___);

「値の破棄」を明示したい

  • _ の数がドンドン増えるのはイヤだ
  • 値を無視するという意図の変数を誤って使わせたくない

そんなお気持ちを C# 7.0 が「discards (値の破棄)」としてサポートします。後述しますが、以下のようにいくつかのシチュエーションでのみアンダースコアが特別扱いを受けます。

//--- こんな関数があったとして
private void OutVariable(out int value)
    => value = 123;

//--- こんな風に書ける
this.OutVariable(out var _);
this.OutVariable(out _);

//--- 触ろうとするとコンパイルエラー
_ += 10;  // そんな変数はないぞ!

実は out var _ のように型を書く必要はなく out _ だけでも OK です。また上記の場合 _ は変数として認められていないため、以下のような書き方をしてもコンパイルエラーになりません。

//--- こんな複数の値を引数から戻す関数があったとして
private void OutVariable2(out int x, out string y)
{
    x = 123;
    y = "abc";
}

//--- 全部 _ ひとつで OK
this.OutVariable2(out var _, out var _);
this.OutVariable2(out _, out _);

discards が利用できる箇所

ザッと調べた範囲では、現状の C# 7.0 では以下の箇所で値の破棄の構文を使うことができます。

//--- 型分解のときに値を受けるけど捨てる
var (name, _) = ("xin9le", 32);
var (_, _) = ("xin9le", 32);

//--- 型スイッチで string 型に対して処理したいけど値は使わない
switch ("abc")
{
    case string _:
        break;
                
    //--- ちなみにコレはコンパイルエラー
    //case _:
    //    break;
}

LINQ やイベントハンドラを記述する際のラムダ式でも頻繁に _ を使うことがありますが、C# 7.0 ではサポートされていません。これは今後のバージョンに期待…かもしれません。

逆コンパイル

この機能がどうやって実現されているのか、いつも通り逆コンパイルしてのぞいてみます。すると、以下のような C# 6 までのフツーのコードに展開されます。

//--- これは
this.OutVariable2(out _, out _);

//--- 素直にこう展開される
int item1;
string item2;
this.OutVariable2(out item1, out item2);

つまり discards の構文であることを C# コンパイラが上手に判断して、良きに計らってくれているということですね。IL レベルでは一切変更がありません。

Visual Studio 2017 (15.1) インストール後に Unity で Windows Store App 向けビルドが通らないときの対処

4/11 から一般提供が開始された Windows 10 Creators Update。このリリースに合わせて Visual Studio 2017 も更新され、内部バージョンが 15.0 から 15.1 になりました。Visual Studio 上ではこんな感じで通知が来ます。

f:id:xin9le:20170423184546p:plain

これをクリックして更新するだけで、インストール状態を判定して全自動でアップデートが完了します。実に素晴らしい!過去に業務で数年インストーラー開発を担当していた自分としては、こういった複雑なアプリケーションのアップデートをユーザーに一切手間を与えないで行うのは本当に尊敬します。だがしかし…!

15.1 に更新するとビルドが通らない

なんということでしょう…。最近は Unity を使って HoloLens のアプリケーション開発をしているのですが、更新しただけでこのビルドが通らなくなってしまったのです。以下のようなエラーが出ます。

f:id:xin9le:20170423185821p:plain

Unity で HoloLens の開発を行うときには HoloToolkit-Unity という (半ば必須とも言える) 公式のライブラリを使用します。使わないで開発する方が普通じゃない、というくらい必ず使います。そして上記のエラーが出ているのはこの HoloToolkit の部分です。自分で作ったものじゃないのでどうしようもない。全自動でアップデートが掛かってこれが起こるとは悪夢としか言いようがない…。

原因

端的に言ってしまうと、原因は Unity が参照している Windows SDK のバージョンが変わってしまったことにあります。Visual Studio 2017 (15.0) の段階では、一緒にインストールされる Windows SDK のバージョンは「10.0.14393」系になります。こんな感じです。

f:id:xin9le:20170423190408p:plain

そして Visual Studio 2017 (15.1) に更新すると「10.0.15063」系がインストールされます。Windows 10 Creators Update 対応版ですね。

f:id:xin9le:20170423194328p:plain

Unity で Windows Store App 向けビルドをする際、この「10.0.15063」系を見に行くようになると先の無慈悲なエラーが出るようになります。そもそもビルドエラーが出るのがオカシイとは思うのですが、対象の Windows SDK (10.0.15063) をアンインストールしてもなぜか改善しません。ワンクリックするだけで全自動で走る素敵な Visual Studio 2017 の更新インストールをするだけで詰みます。もう終わってます。

回避方法

回避方法は 2 つあるでしょう。

1. Visual Studio 2017 (15.0) を使い続ける

簡単です。修正されるまで更新しない。だがしかし、一体いつ修正されるかわからん!そんなことでは日進月歩なこの時代に簡単に置いて行かれてしまう!いつ間違ってワンクリックの超素敵全自動アップデートをしてしまうかわからん!…ので、できれば更新したいというお気持ちだと思います。

2. Unity が参照している Windows SDK のバージョンをちょろまかす

まずまず Unity での Windows Store App 向けビルドが Visual Studio のバージョン自体に直接依存しているわけではありません。必ずどこかの情報を参照して Windows SDK のバージョンを切り替えているはずです。つまり Unity のビルドプロセスでどこを参照しているのかを調べれば OK ということになります。そして気合と勘でいろいろ調べていくと以下のパスにある AssemblyConverter.exe が怪しいと気付きます。

C:\Program Files\Unity\5.x.xxx\Editor\Data\PlaybackEngines\MetroSupport\Tools\AssemblyConverter.exe

この exe は .NET でできているので逆コンパイルして中を覗いていきます。すると、以下のようなコードが出てきます。

f:id:xin9le:20170423192937p:plain

Unity が Windows Store App 向けビルドをする際、以下のレジストリを参照して利用する Windows SDK のバージョンを判定していることが分かります。

項目
キー HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\Windows\v10.0
名前 ProductVersion
データ 10.0.15063

ここの値を「10.0.14393」に書き換えれば OK です。念のためバックアップをしつつ行うと、以下のような感じになります。

f:id:xin9le:20170423193540p:plain

書き換えをしたら Unity を再起動してください。これでビルドが通るようになると思います。

まとめ

ということで、本当に苦労して原因を突き止めました。互換のない状態になっている Windows SDK 自体に問題がある気がしないでもないですが、これでまだしばらくは戦えそうです :)