Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

DispatcherFrame徹底解説

ほとんどの人がDispatcherFrameについて、聞いたことはありますよね。それでは、それが何なのか、そしてどこで使えるのを、頑張って理解してみましょう。MSDNにはこうあります:

Dispatcher 内の実行ループを表します。

これだけじゃ理解出来ませんよね。それと、とても使える処理が書いたコード例も乗っているのですが、これもちょっと良くわかりません。こんなのです。:

[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public void DoEvents()
{
  DispatcherFrame frame = new DispatcherFrame();
  Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
     new DispatcherOperationCallback(ExitFrame),
  frame);
  Dispatcher.PushFrame(frame);
}

public object ExitFrame(object f)
{
  ((DispatcherFrame)f).Continue = false;
  return null;
}

この処理はDoEventsと名付けられていて、名前から察するとおり、Windowsイベントを処理することを強制するものですね。さて、ではDispatcherFrameが何なのか理解して、このメソッドがどのように動くのか考えてみましょう。

まず最初にDispatcherクラスから話を始めましょう。WPFを使っている方ならこれが何かは当然お分かりですよね。これはWPFにおいてUIスレッドを処理するのに使うクラスです。処理すべきアイテムのキューを持っているわけですね。みなさんDispatcherについてはご存知だと思いますので細かい説明はここではしません。

Dispatcherがどう動くのか見るために、ildasm.exeを使ってWindowsBaseアセンブリを逆アセンブリしてみましょう。Dispatcherクラスを見つけてそれのILをみてみます。RunメソッドがDispatcherのしごとを開始させるものですね。ではこの中を見てみましょう。:

  IL_0000:newobj
  instance void System.Windows.Threading.DispatcherFrame ::.ctor()
  IL_0005:call
  void System.Windows.Threading.Dispatcher::PushFrame
(class System.Windows.Threading.DispatcherFrame)

DispatcherFrameが新しく作られて、PushFrameメソッドが呼ばれていますね。ではこのメソッドの実装も見てみましょう(ここでは重要な行のみ示してます)::

IL_0039:ldarg.0
IL_0043:call
  instance bool System.Windows.Threading.Dispatcher::GetMessage(valuetype
  System.Windows.Interop.MSG&, int, int32, int32)

IL_004d:
call instance void
  System.Windows.Threading.Dispatcher::TranslateAndDispatchMessage(valuetype
  System.Windows.Interop.MSG&)

IL_0053:
  callvirt instance bool System.Windows.Threading.DispatcherFrame::get_Continue()
IL_0058:
  brtrue.s IL_0039

わかりやすく書くと、こういう風になります::

While (dispatcherFrame.Continue)
{
  Dispatcher.GetMessage();
  Dispatcher.TranslateAndDispatch();
}

つまり、dispatcherFrameのContinueプロパティがTrueの間、システムメッセージを読んで解釈するというサイクルが実行され続けるわけです。

MSDNの記述に戻ってみましょう。

Dispatcher 内の実行ループを表します。

まさにこのWHILE文がDispatcherFrameとして表現されていますね。

アプリケーションが開始すると、Dispatcher.Runが呼ばれます。DispatcerはデフォルトのDispatcherFrameを作成し、そこでアプリケーションのメインメッセージループを回します。Dispatcher.PushFrameを呼ぶことで上に書いたような新しいループが開始されるわけです。この時メインループは、新しいDispatcherFrame.Continueがfalseを返すまで「停止」します。

それではWPFのDoEventに戻ってみましょう。

[SecurityPermissionAttribute(SecurityAction.Demand,
Flags = SecurityPermissionFlag.UnmanagedCode)]
public void DoEvents()
{
  DispatcherFrame frame = new DispatcherFrame();
  Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
     new DispatcherOperationCallback(ExitFrame), frame);
  Dispatcher.PushFrame(frame);
}

public object ExitFrame(object f)
{
  ((DispatcherFrame)f).Continue = false;
  return null;
}

セキュリティ関係の属性はここでは飛ばして先に行きましょう。UIスレッドで実行しないといけない、非常に重い処理があった、んでもってその間にGUIをアップデートしないといけない、となった時のことを考えてみて下さい。

どうやってこれを解く??

DoEventでは、DispatcherFrameを作成して一度だけ優先度バックグラウンドにしたdispatcher上で「実行」します。この時このDispatcherFrameが実行され続けないようにします(frame.Continue = falseがその部分です)背景で実行するってことは、一番優先度が低いわけですね。

DoEventを呼んだときには、処理しなければいけないアイテムがdispatcherにはたまっています。そしてその中には、すぐに更新しないといけないGUIの更新処理が含まれています。PushFrameが呼ばれると、メインループは中断され、上で書いた新しいループが走り始めます。Dispatcherはシステムメッセージを受け取り、優先度をみながらアイテムキューを処理していきます。Dispatcherは全てのアイテムを処理していくのですが、その中に新しいループを止める操作も入っているわけです。これは優先度最低で追加しているので、最後に処理されます。ループを止める操作が実行されると、新しいループはとまり、フローはメインループにもどって、また重い処理を再開します。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment