استخدم النمط غير المتزامن القائم على المهام (TAP) لتشغيل كود Revit API من أي سياق تنفيذ.
中文说明
إذا سبق لك أن واجهت استثناء Revit API الذي يقول، "لا يمكن تنفيذ Revit API خارج سياق Revit API"، عادةً عندما تريد تنفيذ تعليمات برمجية Revit API من نافذة غير مشروطة، فقد تحتاج إلى هذه المكتبة لإنقاذ حياتك.
الحل الشائع لهذا الاستثناء هو تغليف كود Revit API باستخدام IExternalEventHandler
وتسجيل نسخة المعالج في Revit مسبقًا للحصول على مشغل ( ExternalEvent
). لتنفيذ المعالج، ما عليك سوى رفع المشغل من أي مكان لوضع المعالج في قائمة الانتظار في حلقة أوامر Revit. ولكن هناك مشكلة أخرى تأتي. بعد رفع الزناد، في نفس السياق، ليس لديك أي فكرة عن متى سيتم تنفيذ المعالج وليس من السهل الحصول على بعض النتائج الناتجة عن هذا المعالج. إذا كنت تريد تحقيق ذلك، فيجب عليك إعادة عنصر التحكم يدويًا إلى سياق الاستدعاء.
يبدو هذا الحل مشابهًا تمامًا لآلية "الوعد" إذا كنت معتادًا على JavaScript ES6. في الواقع، يمكننا تحقيق كل المنطق المذكور أعلاه من خلال الاستفادة من النمط غير المتزامن القائم على المهام (TAP) والذي يُعرف عمومًا باسم Task<T>
في .NET. من خلال اعتماد Revit.Async، من الممكن تشغيل كود Revit API من أي سياق، لأن Revit.Async داخليًا يغلف التعليمات البرمجية الخاصة بك تلقائيًا باستخدام IExternalEventHandler
وينتج القيمة المرجعة إلى سياق الاستدعاء لجعل استدعاءك أكثر طبيعية.
إذا لم تكن على دراية بالنمط غير المتزامن القائم على المهام (TAP)، فإليك بعض المواد المفيدة عنه المقدمة من Microsoft:
فيما يلي رسم تخطيطي يقارن آلية الحدث الخارجي لـ Revit API مع Revit.Async ولقطات شاشة للجزأين الرئيسيين:
لقد تم سؤالي كثيرًا عما إذا كان Revit.Async يقوم بتشغيل Revit API في سلسلة محادثات في الخلفية.
دعونا توضيح ذلك. الجواب لا !!!!! لا تنخدع بكلمة "غير متزامن".
كلمة "Async" ليست في الواقع بريئة هنا. إن .NET هو الذي يقوم بتسمية مجموعة من أساليب مؤشرات الترابط المتعددة بنهاية "Async" التي تؤدي إلى سوء الفهم العام.
يمكن تفسير هذا السؤال بدءًا من الاختلافات بين البرمجة غير المتزامنة والبرمجة متعددة الخيوط.
كلمة من تدفق المكدس:
"الترابط يتعلق بالعاملين؛ أما عدم التزامن فهو يتعلق بالمهام".
تشبيه من نفس إجابة stackoverflow :
أنت تطبخ في مطعم. يأتي طلب للبيض والخبز المحمص.
متزامن: تقوم بطهي البيض، ثم تقوم بطهي الخبز المحمص.
غير متزامن، ذو خيط واحد: تبدأ عملية طهي البيض وتضبط مؤقتًا. تبدأ عملية طهي الخبز المحمص، وتضبط مؤقتًا. بينما كلاهما يطبخان، تقومين بتنظيف المطبخ. عندما يحين موعد انتهاء الوقت، ارفعي البيض عن النار وأخرجي الخبز المحمص من المحمصة وقدميهما.
غير متزامن ومتعدد الخيوط: يمكنك استئجار طباخين آخرين، أحدهما لطهي البيض والآخر لطهي الخبز المحمص. الآن لديك مشكلة التنسيق بين الطهاة بحيث لا يتعارضون مع بعضهم البعض في المطبخ عند مشاركة الموارد. وعليك أن تدفع لهم.
السبب وراء سوء فهم الأشخاص لـ "غير متزامن == متعدد الخيوط" هو أن غير المتزامن لديه فرصة كبيرة في الحصول على مؤشرات ترابط متعددة. في معظم تطبيقات واجهة المستخدم (STA)، عندما نستخدم مؤشرات ترابط متعددة لتشغيل مهمة في الخلفية، فإن نتيجة هذه المهمة تحتاج إلى "الرجوع" إلى سلسلة رسائل واجهة المستخدم ليتم تقديمها. يأخذ غير المتزامن دوره في مرحلة "العودة".
في تطبيق نموذج Windows، إذا كنت تريد تحديث واجهة المستخدم من مؤشر ترابط عامل، فأنت بحاجة إلى استخدام طريقة Invoke
لوضع Delegate
في قائمة الانتظار لمؤشر الترابط الرئيسي لإجراء تحديثات واجهة المستخدم.
في تطبيق WPF، إذا كنت تريد تحديث واجهة المستخدم من مؤشر ترابط عامل، فأنت بحاجة إلى استخدام كائن Dispatcher
لوضع Delegate
في قائمة الانتظار لمؤشر الترابط الرئيسي لإجراء تحديثات واجهة المستخدم.
في عالم ريفيت، هو نفسه تقريبا. يتم استخدام Revit API لتحديث النماذج. يقوم Revit بإجراء تحديثات النموذج على الموضوع الرئيسي ويتطلب استدعاء جميع واجهات برمجة التطبيقات على الموضوع الرئيسي أيضًا، من أجل سلامة الموضوع على ما أعتقد.
إذا كنت ترغب في تحديث النماذج من سلسلة عمليات، فأنت بحاجة إلى استخدام كائن ExternalEvent
لوضع قائمة الانتظار ( Raise()
) لمثيل IExternalEventHandler
في سلسلة المحادثات الرئيسية لاستدعاء Revit API. هذا هو النمط غير المتزامن الذي يوفره Revit لجدولة استدعاءات API الجديدة.
أما بالنسبة لـ Revit.Async، فهو مجرد غلاف حول النمط غير المتزامن أعلاه. الهدف من هذه المكتبة هو توفير تجربة غير تقليدية لواجهة برمجة تطبيقات Revit غير المتزامنة.
بالتأكيد لا يوجد شيء متعدد الخيوط في 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] إذا كان لديك أي مشكلة في استخدام هذه المكتبة.