タスクベースの非同期パターン(TAP)を使用して、任意の実行コンテキストから Revit API コードを実行します。
中文说明
「Revit API コンテキスト外では Revit API を実行できません」という Revit API 例外が発生したことがある場合 (通常はモードレス ウィンドウから Revit API コードを実行する場合)、命を救うためにこのライブラリが必要になる可能性があります。
この例外に対する一般的な解決策は、 IExternalEventHandler
使用して Revit API コードをラップし、事前にハンドラ インスタンスを Revit に登録してトリガー( ExternalEvent
)を取得することです。ハンドラを実行するには、どこからでもトリガーを上げて、ハンドラを Revit コマンド ループのキューに入れるだけです。しかし、別の問題が発生します。トリガーを呼び出した後、同じコンテキスト内でハンドラーがいつ実行されるかはわかりません。また、そのハンドラーから生成された結果を取得するのは簡単ではありません。これを実現したい場合は、手動でコントロールを呼び出し元のコンテキストに戻す必要があります。
JavaScript ES6 に精通している場合、このソリューションは「Promise」のメカニズムに非常によく似ています。実際には、.NET では一般にTask<T>
として知られるタスクベースの非同期パターン (TAP) を利用することで、上記のロジックをすべて実現できます。 Revit.Async を採用することで、Revit API コードを任意のコンテキストから実行できるようになります。これは、Revit.Async が内部的にコードをIExternalEventHandler
で自動的にラップし、戻り値を呼び出しコンテキストに渡して呼び出しをより自然にするためです。
タスクベースの非同期パターン (TAP) に慣れていない場合は、Microsoft が提供するそれに関する役立つ資料を次に示します。
以下は、Revit API の外部イベント メカニズムと Revit.Async を比較した図と、2 つの主要部分のスクリーンショットです。
Revit.Async がバックグラウンド スレッドで Revit API を実行するかどうかについてよく質問されました。
それを明確にしましょう。答えはノーです!!!! 「非同期」という言葉に惑わされないでください。
「非同期」という言葉は、ここでは実際には無害です。一般的な誤解を招く原因となっているのは、多数のマルチスレッド メソッドに「Async」という語尾を付けた名前を付けているのは .NET です。
この疑問は、非同期プログラミングとマルチスレッド プログラミングの違いから説明できます。
stackoverflow からの一言:
「スレッド化はワーカーに関するものであり、非同期はタスクに関するものです。」
同じ stackoverflow の回答からの類推:
あなたはレストランで料理をしています。卵とトーストの注文が入ります。
同期: 卵を調理してからトーストを調理します。
非同期、シングルスレッド: 卵の調理を開始し、タイマーを設定します。トーストの調理を開始し、タイマーをセットします。二人が料理をしている間、あなたはキッチンを掃除します。タイマーが鳴ったら、卵を火から下ろし、トースターからトーストを取り出して提供します。
非同期、マルチスレッド: さらに 2 人の料理人を雇い、1 人は卵を調理し、もう 1 人はトーストを調理します。ここで、リソースを共有するときにキッチン内で料理人が互いに競合しないように調整するという問題があります。そしてあなたは彼らに支払わなければなりません。
「非同期 == マルチスレッド」という誤解がある理由は、非同期にはマルチスレッドが伴う可能性が大きいためです。ほとんどの UI アプリケーション (STA) では、マルチスレッドを使用してバックグラウンド タスクを実行する場合、そのタスクの結果が表示されるように UI スレッドに「戻る」必要があります。非同期は「戻る」フェーズで役割を果たします。
Windows フォーム アプリケーションで、ワーカー スレッドから UI を更新する場合は、 Invoke
メソッドを使用してDelegate
メイン スレッドにキューに入れ、UI の更新を実行する必要があります。
WPF アプリケーションでワーカー スレッドから UI を更新する場合は、 Dispatcher
オブジェクトを使用してDelegate
メイン スレッドにキューに入れ、UI の更新を実行する必要があります。
Revit の世界でも、それはほぼ同じです。モデルの更新には Revit API が使用されます。 Revit はモデルの更新をメイン スレッドで実行しますが、スレッドの安全性のために、すべての API もメイン スレッドで呼び出す必要があると思います。
ワーカー スレッドからモデルを更新する場合は、 ExternalEvent
オブジェクトを使用してIExternalEventHandler
インスタンスをメイン スレッドにキュー( Raise()
)し、Revit API を呼び出す必要があります。これは、新しい API 呼び出しをスケジュールするために Revit が提供する非同期パターンです。
Revit.Async に関しては、上記の非同期パターンのラッパーにすぎません。このライブラリの目標は、すぐに使用できる非同期 Revit API のエクスペリエンスを提供することです。
Revit.Async にはマルチスレッド機能はありません。
有効な Revit API コンテキストでは、RevitTask の機能を使用する前に RevitTask を初期化します。
RevitTask . Initialize ( app ) ;
有効な Revit API コンテキストの一部は次のとおりです。
Revit.Async の主な機能はRevitTask.RunAsync()
メソッドによって公開されます。 RevitTask.RunAsync()
メソッドには複数のオーバーロードがあります。
Task RunAsync(Action action)
await RevitTask . RunAsync ( ( ) =>
{
// sync function without return value
} )
Task RunAsync(Action<UIApplication> action)
await RevitTask . RunAsync ( ( uiApp ) =>
{
// sync function without return value, with uiApp paramter to access Revit DB
} )
Task<T> RunAsync<T>(Func<T> func)
var result = await RevitTask . RunAsync ( ( ) =>
{
// sync function with return value
return 0 ;
} )
// result will be 0
Task<T> RunAsync<T>(Func<UIApplication, T> func)
var result = await RevitTask . RunAsync ( ( uiApp ) =>
{
// sync function with return value, with uiApp paramter to access Revit DB
return 0 ;
} )
// result will be 0
Task RunAsync(Func<Task> func)
await RevitTask . RunAsync ( async ( ) =>
{
// async function without return value
} )
Task RunAsync(Func<UIApplication, Task> func)
await RevitTask . RunAsync ( async ( uiApp ) =>
{
// async function without return value, with uiApp paramter to access Revit DB
} )
Task<T> RunAsync<T>(Func<Task<T>> func)
var result = await RevitTask . RunAsync ( async ( ) =>
{
// async function with return value, http request as an example
var httpResponse = await http . Get ( " server api url " ) ;
//
return httpResponse ;
} )
// result will be the http response
Task<T> RunAsync<T>(Func<UIApplication, Task<T>> func)
var result = await RevitTask . RunAsync ( async ( uiApp ) =>
{
// async function with return value, with uiApp paramter to access Revit DB, http request as an example
var httpResponse = await http . Get ( " server api url " ) ;
//
return httpResponse ;
} )
// result will be the http response
[ Transaction ( TransactionMode . Manual ) ]
public class MyRevitCommand : IExternalCommand
{
public static ExternalEvent SomeEvent { get ; set ; }
public Result Execute ( ExternalCommandData commandData , ref string message , ElementSet elements )
{
//Register MyExternalEventHandler ahead of time
SomeEvent = ExternalEvent . Create ( new MyExternalEventHandler ( ) ) ;
var window = new MyWindow ( ) ;
//Show modeless window
window . Show ( ) ;
return Result . Succeeded ;
}
}
public class MyExternalEventHandler : IExternalEventHandler
{
public void Execute ( UIApplication app )
{
//Running some Revit API code here to handle the button click
//It's complicated to accept argument from the calling context and return value to the calling context
var families = new FilteredElementCollector ( app . ActiveUIDocument . Document )
. OfType ( typeof ( Family ) )
. ToList ( ) ;
//ignore some code
}
}
public class MyWindow : Window
{
public MyWindow ( )
{
InitializeComponents ( ) ;
}
private void InitializeComponents ( )
{
Width = 200 ;
Height = 100 ;
WindowStartupLocation = WindowStartupLocation . CenterScreen ;
var button = new Button
{
Content = " Button " ,
Command = new ButtonCommand ( ) ,
VerticalAlignment = VerticalAlignment . Center ,
HorizontalAlignment = HorizontalAlignment . Center
} ;
Content = button ;
}
}
public class ButtonCommand : ICommand
{
public bool CanExecute ( object parameter )
{
return true ;
}
public event EventHandler CanExecuteChanged ;
public void Execute ( object parameter )
{
//Running Revit API code directly here will result in a "Running Revit API outside of Revit API context" exception
//Raise a predefined ExternalEvent instead
MyRevitCommand . SomeEvent . Raise ( ) ;
}
}
[ Transaction ( TransactionMode . Manual ) ]
public class MyRevitCommand : IExternalCommand
{
public Result Execute ( ExternalCommandData commandData , ref string message , ElementSet elements )
{
//Always initialize RevitTask ahead of time within Revit API context
// version 1.x.x
// RevitTask.Initialze();
// version 2.x.x
RevitTask . Initialize ( commandData . Application ) ;
var window = new MyWindow ( ) ;
//Show modeless window
window . Show ( ) ;
return Result . Succeeded ;
}
}
public class MyWindow : Window
{
public MyWindow ( )
{
InitializeComponents ( ) ;
}
private void InitializeComponents ( )
{
Width = 200 ;
Height = 100 ;
WindowStartupLocation = WindowStartupLocation . CenterScreen ;
var button = new Button
{
Content = " Button " ,
Command = new ButtonCommand ( ) ,
CommandParameter = true ,
VerticalAlignment = VerticalAlignment . Center ,
HorizontalAlignment = HorizontalAlignment . Center
} ;
Content = button ;
}
}
public class ButtonCommand : ICommand
{
public bool CanExecute ( object parameter )
{
return true ;
}
public event EventHandler CanExecuteChanged ;
public async void Execute ( object parameter )
{
//.NET 4.5 supported keyword, use ContinueWith if using .NET 4.0
var families = await RevitTask . RunAsync (
app =>
{
//Run Revit API code here
//Taking advantage of the closure created by the lambda expression,
//we can make use of the argument passed into the Execute method.
//Let's assume it's a boolean indicating whether to filter families that is editable
if ( parameter is bool editable )
{
return new FilteredElementCollector ( app . ActiveUIDocument . Document )
. OfType ( typeof ( Family ) )
. Cast < Family > ( )
. Where ( family => editable ? family . IsEditable : true )
. ToList ( ) ;
}
return null ;
} ) ;
MessageBox . Show ( $" Family count: { families ? . Count ?? 0 } " ) ;
}
}
弱いIExternalEventHandler
インターフェイスにうんざりしていませんか?代わりにIGenericExternalEventHandler<TParameter,TResult>
インターフェイスを使用してください。これにより、ハンドラーに引数を渡し、完了時に結果を受け取ることができます。
事前定義された抽象クラスから派生することを常にお勧めします。これらは、引数の受け渡しと結果を返す部分を処理するように設計されています。
クラス | 説明 |
---|---|
AsyncGenericExternalEventHandler<TParameter, TResult> | 非同期ロジックの実行に使用します |
SyncGenericExternalEventHandler<TParameter, TResult> | 同期ロジックの実行に使用します |
[ Transaction ( TransactionMode . Manual ) ]
public class MyRevitCommand : IExternalCommand
{
public Result Execute ( ExternalCommandData commandData , ref string message , ElementSet elements )
{
//Always initialize RevitTask ahead of time within Revit API context
// version 1.x.x
// RevitTask.Initialze();
// version 2.x.x
RevitTask . Initialize ( commandData . Application ) ;
//Register SaveFamilyToDesktopExternalEventHandler ahead of time
RevitTask . RegisterGlobal ( new SaveFamilyToDesktopExternalEventHandler ( ) ) ;
var window = new MyWindow ( ) ;
//Show modeless window
window . Show ( ) ;
return Result . Succeeded ;
}
}
public class MyWindow : Window
{
public MyWindow ( )
{
InitializeComponents ( ) ;
}
private void InitializeComponents ( )
{
Width = 200 ;
Height = 100 ;
WindowStartupLocation = WindowStartupLocation . CenterScreen ;
var button = new Button
{
Content = " Save Random Family " ,
Command = new ButtonCommand ( ) ,
CommandParameter = true ,
VerticalAlignment = VerticalAlignment . Center ,
HorizontalAlignment = HorizontalAlignment . Center
} ;
Content = button ;
}
}
public class ButtonCommand : ICommand
{
public bool CanExecute ( object parameter )
{
return true ;
}
public event EventHandler CanExecuteChanged ;
public async void Execute ( object parameter )
{
var savePath = await RevitTask . RunAsync (
async app =>
{
try
{
var document = app . ActiveUIDocument . Document ;
var randomFamily = await RevitTask . RunAsync (
( ) =>
{
var families = new FilteredElementCollector ( document )
. OfClass ( typeof ( Family ) )
. Cast < Family > ( )
. Where ( family => family . IsEditable )
. ToArray ( ) ;
var random = new Random ( Environment . TickCount ) ;
return families [ random . Next ( 0 , families . Length ) ] ;
} ) ;
//Raise your own handler
return await RevitTask . RaiseGlobal < SaveFamilyToDesktopExternalEventHandler , Family , string > ( randomFamily ) ;
}
catch ( Exception )
{
return null ;
}
} ) ;
var saveResult = ! string . IsNullOrWhiteSpace ( savePath ) ;
MessageBox . Show ( $" Family { ( saveResult ? " " : " not " ) } saved: n { savePath } " ) ;
if ( saveResult )
{
Process . Start ( Path . GetDirectoryName ( savePath ) ) ;
}
}
}
public class SaveFamilyToDesktopExternalEventHandler :
SyncGenericExternalEventHandler < Family , string >
{
public override string GetName ( )
{
return " SaveFamilyToDesktopExternalEventHandler " ;
}
protected override string Handle ( UIApplication app , Family parameter )
{
//write sync logic here
var document = parameter . Document ;
var familyDocument = document . EditFamily ( parameter ) ;
var desktop = Environment . GetFolderPath ( Environment . SpecialFolder . DesktopDirectory ) ;
var path = Path . Combine ( desktop , $" { parameter . Name } .rfa " ) ;
familyDocument . SaveAs ( path , new SaveAsOptions { OverwriteExistingFile = true } ) ;
return path ;
}
}
このライブラリの使用に問題がある場合は、[email protected] までお気軽にご連絡ください。