非同期プログラミング-グラフエディター

ビジネスロジックを記述するプロセスでは、内部依存関係を持つ非同期操作のグラフを作成する必要がある場合があります。 タスクが非同期に実行されるが、一部のタスクは他のタスクに依存しているため、開始できるようになるまで「待機」する必要があります。 この投稿では、開発者が依存関係グラフを視覚的に識別できるようにするグラフィカルDSLを作成することにより、この問題を解決する方法を示したいと思います。





注:英語の記事とソースコードはこちら





はじめに



一般的に、ドメイン固有言語(ドメイン固有言語、略してDSL)には3つの形式があります。 最初の種類はテキストDSLで、これはテキストと構造によってのみ定義され、このテキストをコードに変換する特定のプロセスに関連付けられています。 2番目の種類は構造DSLで、コンテンツはツリーまたはグラフのようなエディターを使用して定義されます。 3番目のタイプであるグラフィックDSLについて説明します。開発者はグラフィックエディターを使用して、後でコードに変換できる視覚的な構造を作成します。



この記事では、エンドユーザーが非同期操作を定義できる単純なグラフィカルDSLを作成します。非同期操作は、Pulse&Waitメカニズムを使用して編成されます。 添付の例をコンパイルするには、Visual Studio 2008とVisual Studio SDKが必要です。 Microsoft DSLツール(SDKに含まれています)を使用してDSLを作成します。



問題の説明



Pulse&Waitを使用するのは難しいため、Pulse&Waitメカニズムを使用して整理できる操作のシーケンスを決定できるグラフィカルなDSLを作成します。 特に、エディターで非同期ブロックをドラッグアンドドロップできるようにするとともに、ブロック間の関係を定義して、非同期の依存実行のルールを作成できるようにしたいと考えています。



DSL作成



始める前に、DSLツールを使用する際の最も重要なポイントの概要を説明します。





Visual StudioでDSLプロジェクトを作成するには、[新規プロジェクト]を選択し、[その他のプロジェクトタイプ]→[拡張性]→[ドメイン固有の言語デザイナー]を選択します。









[OK]をクリックすると、作成したDSLの機能の一部を特定できるウィザードが表示されます。





ウィザードでの作業が完了すると、DSL定義フレームワークが取得されます。 Visual StudioでDSL機能を使用したことがない場合、スクリーンショットは少しショックを受けるかもしれません。









次の要素がDSL編集プロセスに関係しています。





それでは、DSLを作成するプロセスを詳しく見ていきましょう。



視覚要素の配置



既に書いたように、ツールボックスには作業する必要があるすべての要素が含まれています。 これらの要素は、「論理」と「視覚」の2つのグループに分けられます。 ロジック要素は、DSLの構造(ドメイン)を定義するものです。 視覚要素は、ユーザーがDSLを操作するときに操作するこれらの長方形、線、およびゆるい要素を反映します。



DSL論理構造の中心概念は、ドメインクラスです。 このクラスは、作業しているサブジェクト領域に応じて、 必要ものを表すことができます 。 非同期操作で作業するため、ドメインクラスの1つはOperation



と呼ばれます。









ドメインクラスはプロパティを持つことができます 。 ユーザーが設定できる値。 Operation



クラスには、エンドユーザーがOperation



オブジェクトのインスタンスをモデルにドラッグした後に決定できるTimeout



Name



およびDescription



プロパティがあります。



小さな問題があります-実際、ユーザーはドメインクラスをモデルに直接ドラッグしません。 代わりに、 OperationShape



モデルに自分自身をドラッグします。これは、 Operation



視覚的な反映です。 このクラスは、 GeometryShape



(同じツールボックスから取得)から形成されます。









Operation



ドメインクラスとOperation



視覚表現を定義したら、それらをバインドする必要があります(そのまま実行すると、何も機能しません)。 これを行うには、ダイアグラム要素マップ要素を使用します。 実際、これは2つの要素を接続し、それらの間の関連付けを定義する線です。 ただし、追加しても、何も機能しません。



要素間の関係



DSLのツールボックスコントロールの作成を始める前に(これは楽しいです)、要素間の関係について話す必要があります。 関係には、埋め込み関係と参照関係の2つのタイプがあります。 埋め込み関係を使用する場合、要素Aは要素Bで完全に囲まれます。たとえば、スイムレーン(大きな水平方向の視覚空間)があり、それにクラス全体を挿入する必要がある場合、埋め込み関係を使用するのは理にかなっています。 コメントを添付する必要のあるブロックだけがあれば、参照関係になります。



特定のタスクに要素を使用する方法を見てみましょう。 缶の「ルート」にExampleModel



要素があります。 この要素の名前も変更しません。なぜなら、 最終的なDSLには表示されません。 モデルにプロセスとコメントが含まれていることを判断するために、それぞれのクラス間の埋め込み関係の線を引き、次の図を取得します。









オレンジ色のボックスは関係を象徴し、両側に名前と基本的な関係があります。 カーディナリティは、DSLデザイナーによって後から規制されるため、エンドユーザーはこれに違反することはできません。 リレーションについては、これらのオレンジ色のボックスの意味は、既に完成したDSLを編集するときに、異なるドメインクラスをバインドできることです。



注: DSLデザイナーは多くのルールをあなたの言語に適用しますが、その1つはすべての要素が何かの一部であることを要求します。 これは、すべての要素を1つの「コンテナ」コンテナに減らす必要があることを意味します。 (DSL == XMLであることを思い出せば、この要件の理由は明らかです。)



埋め込み関係を使用して、DSLにプロセスとコメントの両方がモデル全体の一部であることを伝えました。 これで、参照関係を使用して、プロセスにコメントを付けることができ、これら2つの要素をリンクできることを確認できます。









上記の破線は、参照関係、つまり この場合、操作はコメントを参照するだけで、コメントは含まれません。 もちろん、この関係には独自の視覚要素(操作とコメントを結ぶ線)があります。これについては、これから説明します。



最後にツールボックス



DSLの論理的および視覚的な部分を受け取ったら、ユーザーがこのDSLからデザイナーにアイテムをドラッグできるようにする必要があります。 ここから開始します-DSL Explorerのエディターノードから:







ツールボックスの新しい要素を作成するには、DSL全体を右クリックします。 次のメニューが表示されます。









コネクタと要素の2つのオプションがあります。 コネクターは、要素を接続する線です(矢印が付いている場合もあります)。 要素はブロック型の構造です。



新しい要素を作成した後、F4を押すと、この要素のプロパティが表示されます。









ここで重要なのは、これらのプロパティのいくつかを入力する必要があることです。そうしないと、DSLは起動しません。 明らかに定義する必要があるもののうち、要素を反映するドメインクラスの定義、およびアイコンの定義。 (いくつかのデフォルトのアイコンがすでに提供されているため、自分で作成するのが面倒な場合は、既製のアイコンを使用できます。)



発射!



DSLを作成するプロセスを要約します。



  1. ウィザードを使用して基本的なDSLを作成しました

  2. processなど、必要な概念を表すドメインクラスを追加しました。

  3. ドメインクラス間の関係を追加しました。この例では、操作が一般モデルに属し、コメントがあることを確認しました。 また、操作間の遷移操作、および開始と終了の要素が追加されました。

  4. DSLが使用する視覚要素を特定しました。

  5. 視覚要素をドメインクラスに関連付けます。

  6. ツールボックスコントロールを作成し、対応するクラスに関連付けました。



DSLの準備は半分整っています。視覚的な部分のみを定義しています。 すべてのテンプレートを変換し、言語を起動したら、ついにDSLで遊ぶことができます。









コンセプトの



非同期DSLについて、次のイディオムを特定しました。





実際の例を見てみましょう:朝食を食べるプロセス(私は知っているが、あまり賢くない)。 朝食を作るには、やかんを入れて、トースターにパンを入れる必要があります-任意の順序で。 すべてが準備されている間、ジャムを取得したいのですが、トースターをすでにオンにしている場合のみです。 完成したパンとジャムを手に入れたら、サンドイッチを作ることができます。 そして、サンドイッチとお茶の両方が準備できたときだけ、私は朝食を食べ始めることができます。



DSLを使用すると、プロセス全体を次のように定義できます。









ご想像のとおり、太線は開始から終了までを表し、破線は開始から開始までを表します。



T4を使用してモデルを変換する



視覚的な朝食モデルはDSLとしてのみ存在するため、T4を使用して本格的なコードに変換する必要があります。 幸いなことに、変換を行う必要があるときまでに、モデルは既にXML形式に変換されています。残りは、それをバイパスして必要なものを生成することだけです。



T4での最終結果の生成は、 WriteLine()



(宛先ファイルに行を書き込む)やPush/PopIndent()



(スタック上のインデントの数を保持するPush/PopIndent()



などのいくつかの方法で移動します。



ここではT4変換コードを紹介しません。上記のリンクからダウンロードできます。 代わりに、朝食の定義からDSLが生成するものを示します。



namespace Debugging<br/>

{<br/>

using System.Threading;<br/>

partial class Breakfast<br/>

{<br/>

private readonly object MakeSandwichLock = new object ();<br/>

private readonly object EatBreakfastLock = new object ();<br/>

private readonly object GetJamLock = new object ();<br/>

private bool MakeTeaIsDone;<br/>

private bool ToastBreadIsDone;<br/>

private bool GetJamIsDone;<br/>

private bool MakeSandwichIsDone;<br/>

private bool MakeTeaStarted;<br/>

private bool ToastBreadStarted;<br/>

private bool GetJamStarted;<br/>

private bool MakeSandwichStarted;<br/>

protected internal void MakeTea()<br/>

{<br/>

MakeTeaImpl();<br/>

lock (EatBreakfastLock)<br/>

{<br/>

MakeTeaIsDone = true ;<br/>

Monitor.PulseAll(EatBreakfastLock);<br/>

}<br/>

}<br/>

protected internal void ToastBread()<br/>

{<br/>

lock (GetJamLock)<br/>

{<br/>

ToastBreadIsDone = true ;<br/>

Monitor.PulseAll(GetJamLock);<br/>

}<br/>

ToastBreadImpl();<br/>

lock (MakeSandwichLock)<br/>

{<br/>

ToastBreadIsDone = true ;<br/>

Monitor.PulseAll(MakeSandwichLock);<br/>

}<br/>

}<br/>

protected internal void GetJam()<br/>

{<br/>

lock (GetJamLock)<br/>

if (!(ToastBreadStarted))<br/>

Monitor.Wait(GetJamLock);<br/>

GetJamImpl();<br/>

lock (MakeSandwichLock)<br/>

{<br/>

GetJamIsDone = true ;<br/>

Monitor.PulseAll(MakeSandwichLock);<br/>

}<br/>

}<br/>

protected internal void MakeSandwich()<br/>

{<br/>

lock (MakeSandwichLock)<br/>

if (!(ToastBreadIsDone && GetJamIsDone))<br/>

Monitor.Wait(MakeSandwichLock);<br/>

MakeSandwichImpl();<br/>

lock (EatBreakfastLock)<br/>

{<br/>

MakeSandwichIsDone = true ;<br/>

Monitor.PulseAll(EatBreakfastLock);<br/>

}<br/>

}<br/>

protected internal void EatBreakfast()<br/>

{<br/>

lock (EatBreakfastLock)<br/>

if (!(MakeTeaIsDone && MakeSandwichIsDone))<br/>

Monitor.Wait(EatBreakfastLock);<br/>

EatBreakfastImpl();<br/>

}<br/>

}<br/>

}<br/>







たくさんのコード! ただし、このコードは、定義した構造を反映しています。 あとは、生成された構造を使用するだけです。



namespace Debugging<br/>

{<br/>

partial class Breakfast<br/>

{<br/>

AutoResetEvent eatHandle = new AutoResetEvent( false );<br/>

Random rand = new Random();<br/>

public void Prepare()<br/>

{<br/>

ThreadStart[] ops = new ThreadStart[] {<br/>

MakeTea,<br/>

GetJam,<br/>

ToastBread,<br/>

MakeSandwich,<br/>

EatBreakfast };<br/>

foreach (ThreadStart op in ops)<br/>

op.BeginInvoke( null , null );<br/>

eatHandle.WaitOne();<br/>

}<br/>

private int RandomInterval<br/>

{<br/>

get<br/>

{<br/>

return (1 + rand.Next() % 10) * 100;<br/>

}<br/>

}<br/>

public void MakeTeaImpl()<br/>

{<br/>

Thread.Sleep(RandomInterval);<br/>

Console.WriteLine( "Make tea" );<br/>

}<br/>

public void ToastBreadImpl()<br/>

{<br/>

Thread.Sleep(RandomInterval);<br/>

Console.WriteLine( "Toast bread" );<br/>

}<br/>

public void GetJamImpl()<br/>

{<br/>

Thread.Sleep(RandomInterval);<br/>

Console.WriteLine( "Get jam" );<br/>

}<br/>

public void MakeSandwichImpl()<br/>

{<br/>

Thread.Sleep(RandomInterval);<br/>

Console.WriteLine( "Make sandwich" );<br/>

}<br/>

public void EatBreakfastImpl()<br/>

{<br/>

Thread.Sleep(RandomInterval);<br/>

Console.WriteLine( "Eat breakfast" );<br/>

eatHandle.Set();<br/>

}<br/>

}<br/>

}<br/>







このコードを呼び出した結果は次のようになります。



お茶を入れる

トーストパン

ジャムを取得

サンドイッチを作る

朝食を食べる

すべて完了


もちろん、 Make tea



Toast bread



は異なる順序で表示される場合があります。



おわりに



DSL Toolsは、洗練された強力なツールキットです。 このパッケージの重要な特徴は、定義された後の言語の扱いやすさです。 ここでは、DSLツールを使用した作業を表面的にしか説明できませんでした。 機会とニュアンスはたくさんあります。 この投稿が、誰かが自分の研究を行うよう動機付けられることを願っています。 ■







All Articles