Dispatcher とか Queue とかメッセージループとか

GUIのコードを書いていると、時間のかかる処理をやりたくなることは多々あると思うんですが、それを普通に実行しちゃうとGUIが固まりますよね。

で、C#には、Taskという比較的簡単に使える道具がありまして、例えば、適当なスレッドで実行して、その結果だけを非同期で待つって感じのコードなら、

var result = await Task.Run(() => SomeHeavyCalculation());

という感じで、長時間ブロックしちゃう SomeHeavyCalculation なんて処理も簡単にバックグラウンドで実行出来ちゃいます。まぁ、単発の処理ならこれでほとんど解決。何にも問題なし。

ところが、次のような何らかのオブジェクトに対して処理を行う場合にはいろいろと注意が必要になります。

SomeSharedObject obj;

async Task ProcessAsync()
{
  // これ、安全かなぁ?
  await Task.Run(() => obj.SomeHeavyCalculation());
}

何が危険なのかといえば、このオブジェクトが、スレッドセーフ(複数のスレッド間で共有してOKかどうか)がどうかが怪しいかもしれないからです。

Task.Run は、その時その時で、スレッドプール(いくつかのスレッドをあらかじめ作っておいて、いつでもすぐ使えるようにしておく)内のいずれかのスレッドで実行を開始する感じなので、obj.SomeHeavyCalculation()はいつも同じスレッドで実行されるとも限りませんし、また、await中は、処理が別のメソッドに明け渡されるので、ProcessAsync中に、またProcessAsyncが呼ばれることは普通にあり得ます。つまり、二つのobj.SomeHeavyCalculation()が同時に動いてしまう可能性もあります。
これはヤバい。下手すると一発クラッシュ。そうじゃなくても、データが破壊される可能性があります。

これを適当に解決しようとすると、例えば、

SomeSharedObject obj;

async Task ProcessAsync()
{
  await Task.Run(() => {
    // obj へのアクセスを排他的にする(一度に一つしか実行できない)
    lock (obj)
    {
      obj.SomeHeavyCalculation();
    }
  });
}

みたいなコードを書く羽目になる。lock(モニター)を使って、この処理が同時に複数個動作しないようにしちゃうわけです。
まぁ、これが一か所なら別に大した問題にもならないでしょう。

ただ、この場合、あまりにも多くのProcessAsyncが呼ばれると、スレッドプール内のスレッドの枯渇という恐ろしい事態が待っている可能性があります。スレッドプールにいくつのスレッドが入っているかは環境依存ですが、例えば、16個しかスレッドがなかった場合、この関数が短い間に16回呼ばれると、すべてのスレッドを使い果たしてしまい、他の処理がまったく実行されないという事態に陥る可能性があります。

また、別の側面で考えると、この処理は、同時には一つしか動かないわけで、普通に考えて、スレッドの無駄遣いにもなるでしょう。どう考えてももったいない。

ディスパッチャー (Dispatcher)

さて、こういう問題を解決するには、昔ながらの定石があります。
それは、キュー(FIFO:First-In,First-Out)とか、イベントループとか、ディスパッチャーとかと言われるものです。
仕組みは簡単です。仕事をお願いする方は、キューにお願いしたい仕事を投入して、仕事を請け負う側は、キューに入っている仕事を古いものから順番に処理していくっていう、とってもローテクな方法です。

基本的に、Windowsにしろ、macOSにしろ、iOSにしろ、現代的なGUIのシステムは、GUI処理用に、このキューを持っていて、それをメッセージループだの、イベントループだの、あるいは、COMだと、STA(Single Thread Apartment)とか、そういう名前で呼んでいます。GUIは、基本的には、他のプログラムから投げられたメッセージを一つずつ解析して処理していくことを繰り返すだけです。
そのため、当然、一つのメッセージの処理に時間がかかったり、あるいは、このメッセージ処理の流れをせき止められたりすれば、GUIは固まってしまいます。最初に書いた話ですね。

じゃぁ、なんでそれが便利なのか。
上に書いた、別々のスレッドで同時に動かされてしまうかもしれない問題が解決するからです。一つずつしか処理しないので、当然ながら、同時に処理が動くなんてことはありません。

要は、先ほどの例でいえば、SomeSharedObject に対する処理を専任で行うディスパッチャーを一つ作ってしまえばいいんですね。作りましょう。それ。

で、実はこの手のものは、さっきも書いたとおり、OSが既に実装しています。Windowsなら、メッセージループがそれですし、WPFならば、Dispatcher というそのものズバリの名前のものがあります。

WPF の Dispatcher を使って実装してみる

要は、専用のスレッドを一つ立てて、そこに Dispatcher を置いておけば、さっきの SomeSharedObject に対する処理は全部、それでやれちゃうよねっていう感じです。

で、いきなり、SomeSharedObjectの実装の話になっちゃいますが、SomeHeavyCalculationを直接呼べないようにして、それをディスパッチャー経由で実行するのを、awaitできるようにしちゃいます。

class SomeSharedObject
{
  Dispatcher dispatcher;

  public SomeSharedObject()
  {
    ...
    // スレッドを起動して、そこで dispatcher を実行する
    var dispatcherSource = new TaskCompletionSource<Dispatcher>();
    var thread = new Thread(new ThreadStart(() => {
      dispatcherSource.SetResult(Dispatcher.CurrentDispatcher);
      Dispatcher.Run();
    }));
    thread.Start();
    dispatcher = dispatcherSource.Task.Result; // メンバ変数に dispatcher を保存
  }

  // 重い処理: 外からは直接呼べないようにしちゃう
  private void SomeHeavyCalculation() {...}

  // 外の人にはこっちを呼び出してもらおう
  public async Task SomeHeavyCalculationAsync()
  {
    await dispatcher.InvokeAsync(() => SomeHeavyCalculation());
  }
}

これで、使う人は、余計なスレッドとか考えなくてよくなりました。SomeHeavyCalculationAsync を await するだけで良いので、かなり簡単です。

内部的にも、SomeHeavyCalculationを無茶な呼ばれ方をしたりしなくなるので、心配事がかなり減りました。SomeHeavyCalculationは、必ず、単独でしか実行されません。スレッドの競合とか、面倒な話を考えなくて良くなりました。

みんな幸せ。

WPFのDispatcherの闇

まぁ、このコードには、ほとんど説明してないコンストラクタのコードにヤバい部分があります。
WPFのDispatcherの設計ミスとも言えるのですが、スレッド内で実行する Dispatcher を確実にメンバ変数に保存する部分に闇があります。 Task.Result とかいうあんまり使わない方が良いヤバい奴がいます。詳しくは「ConfigureAwait」とかで調べてみてくださいな。TaskCompletionSourceっていう謎の奴が出てきていますが、こちらもどうしても知りたければ調べてください。
このコンストラクタは、なるべく、await を使ってない、プログラムの最初の方で呼び出してね。さもなくば、プログラムが固まっちゃう(デッドロックしちゃう)っていう問題があるんです。悩ましい。

そして、もう一つ、特大級の闇があります。
WPFのプログラムは、そのプログラム内で実行しているすべての Dispatcher が完全に停止するまで、終了しません。つまり、この Dispatcher を殺さないと、プログラムを[X]ボタンとかで終了しようとしても終了できないのです。なんということだ・・・。

こちらには有効な解決策があります。簡単です。GUIのスレッドというか、フォアグラウンド・ディスパッチャーというか、呼び出し元の表で動いているDispatcherを監視して、そいつが終了するタイミングで、こちらのDispatcherも終了すればいいだけです。

これは、次のようにコンストラクターに書き足せばいいだけです。

class SomeSharedObject
{
  Dispatcher dispatcher;

  public SomeSharedObject()
  {
    ...
    // スレッドを起動して、そこで dispatcher を実行する
    var dispatcherSource = new TaskCompletionSource<Dispatcher>();
    var thread = new Thread(new ThreadStart(() => {
      dispatcherSource.SetResult(Dispatcher.CurrentDispatcher);
      Dispatcher.Run();
    }));
    thread.Start();
    dispatcher = dispatcherSource.Task.Result; // メンバ変数に dispatcher を保存

    // 表のディスパッチャーが終了するタイミングで、こちらのディスパッチャーも終了する
    Dispatcher.CurrentDispatcher.ShutdownStarted += (s, e) =>
      dispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);
  }
  ...
}

iOS/macOSGrand Central Dispatch (GCD)

iOS/macOSにも、Grand Central Dispatchといわれる同様の仕組みがあります。なんか、仰々しい名前ですが、やってることはまるで同じです。こちらの方がWPFよりも便利なのは、スレッド込みで作りこまれているものが用意されていることです。

class SomeSharedObject
{
  DispatchQueue dispatcher = new DispatchQueue("SomeSharedObjectQueue");

  ...

  // 重い処理: 外からは直接呼べないようにしちゃう
  private void SomeHeavyCalculation() {...}

  // 外の人にはこっちを呼び出してもらおう
  public async Task SomeHeavyCalculationAsync()
  {
    await InvokeAsync(() => SomeHeavyCalculation());
  }

  // DispatchQueueをawaitできるようにするラッパー(戻り値を返すことのできるバージョン)
  public async Task<T> InvokeAsync<T>(Func<T> func)
  {
    var tcs = new TaskCompletionSource<T>();
    dispatcher.DispatchAsync(() => {
      try
      {
        tcs.SetResult(func());
      }
      catch (Exception e)
      {
        tcs.SetException(e); // ここで例外をちゃんとキャッチしておく(※下の追記を参照)
      }
    });
    return await tcs.Task;
  }

  // DispatchQueueをawaitできるようにするラッパー(Action用)
  public async Task InvokeAsync(Action action)
  {
    await InvokeAsync(() => { action(); return 0; });
  }
}

全体的には、WPF版よりも簡単なコードであることがわかるでしょう。

※追記(2016/08/13) GCD における例外の扱い

重要なことを書き忘れていたので追記しておきます。
まぁ、例のごとく、上にあるコードは、全部、サンプルなので、いろいろ現実的なことを言い出せば追記することはたくさんあるのですが、
XamarinのDispatchQueue.DispatchAsyncは、内部的には、例外をそのままディスパッチャー内にぶちまけちゃう実装になっているんですが、これについては、

https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/developer.apple.com

には、以下のように、例外とか投げたら知らんぜって書かれています。

IMPORTANT
GCD is a C level API; it does not catch exceptions generated by higher level languages. Your application must catch all exceptions before returning from a block submitted to a dispatch queue.

試しに、上記の InvokeAsync で呼び出すActionの中で例外を放ったりすると、

=================================================================
Got a SIGABRT while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries 
used by your application.
=================================================================

みたいな感じでアプリごと死にます。
なので、例外の処理は明示的にやってやる必要があります。

まとめ(適当)

ここまで見れば、WPFiOS/macOSで同じインターフェイスでこのディスパッチャーを実装できそうなことはすぐにわかると思います。実際にできます。

また、ここでは書いていませんが、Androidでも、Handlerというものを使って、ほぼ同等の事が簡単にできます(どこかで暇になったら加筆します)。

要は、どこのプラットフォームでも、基本的には、同じ考え方が通用するということです。そこが一番重要です。

sysctlbyname でデバイスの種類を取得する

iOSiPad mini 2 とか、そういう識別子がとりたかったんですが、 UIDevice.CurrentDevice だと、 iPad だとか適当な種類しか取れず、どうやって調べるんだろう?ってググったら、

developer.apple.com

を使えばいいことは割とすぐに出てきます。
で、今をときめく Swift のサンプルなんかは比較的たくさん見つかるんですが、 Xamarin のものについては、あんまり出てきません。出てきたのは、これぐらい。

forums.xamarin.com

ところがですね。こいつ、資料としては古くて、いろいろ問題があるんです。

  • Unified API じゃない (MonoTouch.Constants.SystemLibraryってもはやない!)
  • 64-bit のことがちゃんと考慮されてない (最後の引数が uint)
  • 確保したメモリを開放してない(最低)
  • というか、p/invoke の使い方がダサい

という、主に最後の理由で、自分で書き直そうって思いました。
SysCtl.HwMachine で、内部のモデル識別子が得られます。
これを人間が読める奴に変換したい人は、下のページでググればいいと思います。

Models - The iPhone Wiki

おまけで、SysCtl.HwMemSize っていうメモリサイズが取得できる奴も搭載してみました。サンプルですね。

public static class SysCtl
{
  /// <summary>
  /// Internal model description.
  /// For the full list of the values, see the following URL:
  /// https://www.theiphonewiki.com/wiki/Models
  /// </summary>
  public static string HwMachine => GetSystemProperty("hw.machine");
  /// <summary>
  /// Memory equiped on the machine.
  /// </summary>
  public static long HwMemSize => GetSystemProperty64("hw.memsize");

  public static string GetSystemProperty(string name)
  {
    IntPtr len64 = IntPtr.Zero;
    if (sysctlbyname(name, null, ref len64, IntPtr.Zero, IntPtr.Zero) < 0)
      return null;
    var len = len64.ToInt32();
    if (len == 1)
      return "";
    var buf = new byte[len];
    if (sysctlbyname(name, buf, ref len64, IntPtr.Zero, IntPtr.Zero) < 0)
      return null;
    return Encoding.ASCII.GetString(buf, 0, len - 1); // removing the terminating \0.
  }

  public static long GetSystemProperty64(string name)
  {
    long val;
    var len = new IntPtr(Marshal.SizeOf<long>());
    if (sysctlbyname(name, out val, ref len, IntPtr.Zero, IntPtr.Zero) < 0)
      return -1;
    return val;
  }

  /// <summary>
  /// https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man3/sysctlbyname.3.html
  /// </summary>
  /// <param name="property"></param>
  /// <param name="buffer"></param>
  /// <param name="oldlenp"></param>
  /// <param name="_null1"></param>
  /// <param name="newlen"></param>
  /// <returns></returns>
  [DllImport(ObjCRuntime.Constants.SystemLibrary)]
  static extern int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string property, byte[] buffer, ref IntPtr oldlenp, IntPtr _null1, IntPtr newlen);

  [DllImport(ObjCRuntime.Constants.SystemLibrary)]
  static extern int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string property, out long value, ref IntPtr oldlenp, IntPtr _null1, IntPtr newlen);
}

HttpStream を .NET Platform Standard 1.1 対応のパッケージとして NuGet で公開しました

実は、以前に HttpStream というライブラリを作ってました。

espresso3389.hatenablog.com

これ、普通に HttpClient で ReponseStream とかをランダムアクセスしようとすると、対応してないぜって感じで例外が出て悲しいので、うまーい具合にラッパーを作ることで、HTTP上のリソースにあたかもランダムアクセスできているかのようにしちゃうというもので、まぁ、便利なので、弊社プロダクトでも利用していたわけです。

NuGet化

で、血迷って、このライブラリは、GitHub で公開したのに、弊社プロダクトは、社内Gitサーバーで管理されているという状態で、そんな状態にもかかわらず、なぜか、git submodule 使ってしまったので、泣きそうな目にあっているという酷い early summer だったんですが、モジュールとしてはかなり安定もしてるので、この際、NuGet にアップロードしてバイナリ運用の方が良くない?みたいな感じになりました。

.NET Platform Standard

https://www.microsoft.com/net/core

で、このライブラリ自体は、今まで、 Profile259 の PCL だったわけですが、そういや、.NET Core 1.0リリースされたよなぁ。ってことで、あの辺もちゃんとサポートするにはどうするんだっけ?って調べてたら、

github.com

PCL から、.NET Platform Standard ってやつに切り替えようぜって書いてありました。
PCL は Profile がもうぐちゃぐちゃで、Xamarin プロフェショナルが謎の番号を暗唱するっていう最悪な状態でしたが、この辺が綺麗に整理されて、.NET Platform Standard のバージョンいくつみたいな感じで管理できるようになりました。
マトリックスも非常にわかりやすい。まぁ、Windows 8って何だっけ?みたいな記憶のかなたになっていることもありますけどw

で、Profile259 なら、.NET Platform Standard 1.0 なんですが、もはや、 Silverlight 要らんのじゃ疑惑もあり、.NET Platform Standard 1.1 でってことになりました。

この辺の移行作業については、次のページは参考になるかもしれませんが、ならないかもしれません。

xamarinhelp.com

このページでも書かれているように、既に NuGet 配布しているパッケージを無理に .NET Platform Standard に移行すると、そのパッケージに依存しているプログラムがぶっ壊れるというか、.NET Platform Standard 移行の道連れになるのでお勧めできませぬ。
別パッケージとしてリリースしなおす方がよいでしょう。

まぁ、要はまだ過渡期過ぎて、ちょっと痛みを伴いますなぁという話ですね。

僕の場合は、 HttpStream は、NuGet 初デビューなので、何にも問題ありませんでした。
また、あと、良い点としては、PCL の時には、Microsoft.Net.HttpClient など、BCL 系のパッケージに依存している形だったのが、.NET Platform Standard では、この部分が、フレームワーク側に統合される形になったので、要は、何にも依存しないパッケージになるので、管理は凄く楽になりました。

公開したパッケージ

NuGet のパッケージはこいつ:

www.nuget.org

GitHub

github.com

使う側からすれば、ビルドしなくて良い。空から降ってくる感があるので、非常に楽になりました。

Xamarin.Forms の ListView で System.MissingMethodException: Default constructor not found for type

※2016/08/31追記:今更ながら、AOT関係ないじゃんっていうツッコミもらったので、AOTっていうのを消しておきます。正しくは、単に、リンカのstrip処理ですね・・・。

突然ですが、 SIN@SAPPOROWORKS の記事を引用します:

みたいなコードを見て、 Xamarin.Forms 簡単だ!的な感じでコーディング始めよう・・・ってしてみたら、 iOS

System.MissingMethodException: Default constructor not found for type ...

みたいな例外を食らうことがあります。
これ、Xamarin.iOS でいうところの、 AOT で有るが故の制限なんですが、用は、コンストラクタが見つからないよって言われています。

そんなこと言っても、ちゃんと public で引数なしのコンストラクタは用意しているし、なんで見つからないんだよ・・・って話になるわけですが、これにはちゃんとした理由があります。

AOT のコンパイラリンカは、基本的には、コード中で利用されていない、要らないコードを最終的なプログラムから削除しようとします。これは、プログラムのサイズを小さくしようという涙ぐましい努力の一部です。

AOT のコンパイラリンカはそこそこ賢いので、この、「要らないコードを削除する」プロセスはある程度までは正確に処理ができるんですが、実は、本当は必要なコードも削除してしまうことがあります。

それはどんなときなのかというと、いわゆる、リフレクション経由の呼び出しというのがそのほとんどの場合です。

こういうクラスがあるとします:

class A
{
  public A() {}
}

次の例は、おそらく問題無くリンクできるようなパターンです:

var a = new A();

一方で、次の様な奴はダメな可能性が高いです:

var a = (A)Activator.CreateInstance(typeof(A));

このパターンは、A という型への参照は残るものの、コンストラクタへの明示的(直接的)な呼び出しがないため、コンストラクタが削除されてしまうことがあるようです。

Visual Studio で CodeLens が有効になっている場合は、もっと分かりやすい話、0 Referencesってなっているコードはヤバイです。

f:id:espresso3389:20150127041232p:plain

これ、この部分を呼び出しているコードが見つからないってことですからね。真っ先に削除の対象になってしまいます。

さて、理由は簡単なので、逆に、どっかに、こそっと、

var a = new A();

みたいなコードを挿入しておけば、それでコンストラクタがちゃんと残るわけですが、普通、そんな変なコードを入れたくはありません。

このために、 Xamarin.iOS には、 PreserveAttribute というものが用意されており、これを使って、

using Foundation; // Xamarin.iOS で PreserveAttribute を使うときにはこれが必要

...

[Preserve(AllMembers=true)]
class A
{
  public A() {}
}

とやると、このクラスのメンバは全部大事だから消さないでねっていう指示を出すことが出来ます。PCLじゃなければ。

そんなことを言っても、私は PCL を作ろうとしているので、そんな変なクラスは使えませんという場合には、

http://developer.xamarin.com/guides/ios/advanced_topics/linker/#Preserving_Code

に書かれているように、好きな namespace に自分で、 PreserveAttribute を作れば良いです。

class PreserveAttribute : System.Attribute
{
  public bool Conditional { get; set; }
}

そうして、この場合、さっきの PreserveAttribute とは微妙に使い方が違います。こいつは、削除して欲しくないメソッドなどに直接付与します。つまり、

class A
{
  [Preserve(Conditional=true)]
  public A() {}
}

みたいにして使います。

さて、本題に戻ると、さっきの SIN@SAPPOROWORKS さんのサンプルですが、

//ListViewの生成
var listView = new ListView{
  ItemTemplate = new DataTemplate(typeof (MyCell)),//セルの指定
  ItemsSource = _tweets,//データソースの指定
  HasUnevenRows = true,//行の高さを可変とする
};

っていうコードが MyCell っていうクラスを作るためのコードなんですが、これ、上記の例と同じで、明示的(直接的)なコンストラクタの呼び出しがありません。
うまく行く場合もあるので(何でか分かりません)、絶対に動作しないとは言わないのですが、死ぬ場合には、上記の通り、コンストラクタに PreserveAttribute を付与すれば直ります。

このコードの場合は、

//セル用のテンプレート
private class MyCell : ViewCell {
  public MyCell() {
...

ってなってるところを、

//セル用のテンプレート
private class MyCell : ViewCell {
  [Preserve(Conditional=true)]
  public MyCell() {
...

ってやっておくほうが安全という話です。

Xamarin、便利なんですけど、この手の???っていうトラップが多くて初心者には辛い部分もありますね。慣れると、あーまた例の奴かって感じになるわけですが。

という他人のふんどしで相撲を取るような記事でした。

.NET の Stream を CoreGraphics で利用する

※2015/1/23 追記
下記の文書によれば、32-bit環境においても、 sizeof(off_t)=8 です。僕は何となく新しい環境でしかテストしていなかったため、問題が起きませんでしたが、当初、sizeof(off_t)=4と誤解してコードを記述していました。
そのため、 position などが nint になっていましたが、
正しくは、 off_t は long に置き換えないといけません。

Xamarin.iOS でコードを書く場合でも、せっかく、 PCL でコードが共有できるわけですから、できるなら .NET の Stream クラスを使った様々な抽象化のお世話になりたいところです。

画像の入力を行う場合などに、ファイル名や URL を直接渡せる場合には、それらの API を利用すれば良いですが、そうではない場合、 CGDataProvider のお世話になります。

この CGDataProvider は、ストレージへのアクセスを抽象化しており、ストリーム型のデータだけでなく、ランダムアクセス可能ではあるもののメモリ上でもファイル上でもないといったデータストレージに対しても CGDataProviderDirectCallbacks が定義するコールバック関数群を利用してアクセス出来ます。

この構造体の定義は次みたいな感じです:

struct CGDataProviderDirectCallbacks
{
  unsigned int version;
  CGDataProviderGetBytePointerCallback getBytePointer;
  CGDataProviderReleaseBytePointerCallback releaseBytePointer;
  CGDataProviderGetBytesAtPositionCallback getBytesAtPosition;
  CGDataProviderReleaseInfoCallback releaseInfo;
};

typedef struct CGDataProviderDirectCallbacks CGDataProviderDirectCallbacks;

version は、現状、 0 で良くて、
次に、 getBytePointer, releaseBytePointer はサポートしなくて良いなら NULL でも良く、実際に実装すべきは、 getBytesAtPosition だけです。

さて、この関数は、

size_t getBytesAtPosition(void *info, void *buffer, off_t position, size_t count )
{
  // info で示されるストレージの position の場所から、
  // count バイトのデータを取得して buffer にぶち込む
  // 戻り値は読み込んだバイト数
}

みたいな簡単な関数です。ここから、 Stream.Position, Stream.Read などを呼び出せばそのまま buffer にデータをぶち込めますね・・・・・・・いや無理。

残念なことに、 Stream.Read はバッファーとしては、 .NET の配列しか受け付けないので、一時的にバイト配列を確保して、そっちから、 Marshal.Copy などを使って buffer にコピーするしかないですね・・・。本当に残念ですが。

まぁ、あとは一時バッファのサイズをいくつにするとか、これループ回すの?みたいな話はありますが、全体的には些末な話です。

あと、残りの releaseInfo ですが、 CGDataProvider が削除されるタイミングで、 Stream.Dispose を呼び出したいといった理由があれば、実装すべきでしょう。

void releaseInfo(void* info)
{
  // データの解放など
}

P/Invoke

さて、ここまで分かれば、これら構造体、コールバックの定義を C# 側でやってあげるのは簡単です。

[DllImport(Constants.CoreGraphicsLibrary)]
static extern IntPtr CGDataProviderCreateDirect(IntPtr info, long size, CGDataProviderDirectCallbacks callbacks);

[StructLayout(LayoutKind.Sequential)]
struct CGDataProviderDirectCallbacks
{
    public int version;
    public IntPtr getBytePointer;
    public IntPtr releaseBytePointer;
    public CGDataProviderGetBytesAtPositionCallback getBytesAtPosition;
    public CGDataProviderReleaseInfoCallback releaseInfo;
};

// Unified API じゃない場合には、 nint の代わりに、 IntPtr を使ってください
delegate nint CGDataProviderGetBytesAtPositionCallback(IntPtr info, IntPtr buffer, long position, nint count);
delegate void CGDataProviderReleaseInfoCallback(IntPtr info);

ということで、この程度だったら、インラインで new するぜー、みたいなノリで、

Stream stream = ...;

var callbacks = new CGDataProviderDirectCallbacks {
  getBytesAtPosition = (info, buffer, position, count)
  {
    // バッファのサイズとか手抜きバージョン
    var buf = new byte[count];
    stream.Position = position;
    var ret = stream.Read(buf, 0, count);
    Marshal.Copy(buf, 0, buffer, ret);
    return ret;
  }
};
var dataProv = CGDataProviderCreateDirect(IntPtr.Zero, size, callbacks);

みたいなことをしたくなりますが、これ、動きません。
理由は、 Xamarin.iOS では、JIT が使えない制約の一部として、 P/Invoke する関数に渡すことが出来る delegate は、 static じゃないといけない(オブジェクトのメソッドを呼び出せない or クロージャ(ラムダ関数)を呼び出せない)という制約があります。

なので、関数は分けて書かないと行けません。
つまりですね、 stream とか、そういう必要なデータは、 CGDataProviderCreateDirect の第一引数 info として渡さないといけない訳ですね。

そうすると、 GCHandle のお世話にならないと行けませんが、 GCHandle は解放し忘れるとオブジェクトを道連れにしてリークするので、 releaseInfo も必然的に実装することになります。

あとは、 Xamarin.iOS には、CGDataProviderが定義されており、さらに、 CGDataProviderRef を IntPtr の形で取れるコンストラクタもあるので、それを使えば、 CGDataProvider が完成します。

class CGStreamDataProvider
{
  private CGDataProviderFromStream() { }

  Stream _stream;
  bool _disposeOnClose;

  public static CGDataprovider Create(Stream stream, bool disposeOnClose)
  {
    var obj = new CGStreamDataProvider() {
      _stream = stream,
      _disposeOnClose = disposeOnClose };
    var callbacks = new CGDataProviderDirectCallbacks
            {
                getBytesAtPosition = getBytesAtPosition,
                releaseInfo = releaseInfo
            };
    var gcHandle = GCHandle.Alloc(obj, GCHandleType.Normal);
    var dataProv = CGDataProviderCreateDirect(GCHandle.ToIntPtr(gcHandle), (nint)stream.Length, callbacks);
    // CGDataProvider は Xamarin.iOS で定義されている
    return new CGDataProvider(dataProv);
  }

  [MonoPInvokeCallback(typeof(CGDataProviderGetBytesAtPositionCallback))]
  static nint getBytesAtPosition(IntPtr info, IntPtr buffer, long position, nint count)
  {
    // GCHandle からオブジェクトのリファレンスを復帰
    var pthis = (CGStreamDataProvider)GCHandle.FromIntPtr(info).Target;
    // バッファのサイズとか手抜きバージョン
    var buf = new byte[count];
    pthis ._stream.Position = position;
    var ret = pthis ._stream.Read(buf, 0, count);
    Marshal.Copy(buf, 0, buffer, ret);
    return ret;
  }

  [MonoPInvokeCallback(typeof(CGDataProviderReleaseInfoCallback))]
        static void releaseInfo(IntPtr info)
  {
    var gcHandle = GCHandle.FromIntPtr(info);
    var pthis = (CGStreamDataProvider)gcHandle.Target;

    // ストリームを破棄すべきだったら破棄する
    if (pthis._disposeOnClose)
      pthis._stream.Dispose();

    // GCHandle はちゃんと解放する
    gcHandle.Free();
  }
}

// 使い方例
var dataProv = CGStreamDataProvider.Create(stream, true);

上記のコードはエラー処理などが大幅に省略されているので、実コードは下記を参考にしてください。