البرمجة

أفضل الممارسات لاستخدام await/async في تطبيقات WPF

عند البدء في استخدام آلية await/async في تطبيقات .Net WPF الخاصة بنا، يمكن أن يكون اختيار كيفية تنظيم الأعمال الجارية من خلالها أمرًا محيرًا. في ViewModel الخاص بي، أقوم باستدعاء طريقة async على خدمة ما. السؤال هو: ما هو الأفضل؟

  1. هل يُفضل أن أقوم بتنفيذ وظيفة await/async داخل هذه الخدمة مباشرةً، عبر استخدام return await Task.Run(()=>{...});؟
  2. هل يجب أن تكون جميع الوظائف الفرعية داخل هذه الخدمة أيضًا async، ثم نستخدم Task.Run داخلها؟

أتصور مثالين:

  1. في هذا النهج، نقوم بتنفيذ الوظائف داخل Task.Run:
csharp
public class Service : IService{ public async Task SomeMethod(SomeParameter parameter){ return await Task.Run(() => { CopyStuff(parameter.A); UpgradeStuff(parameter.B); return ReloadStuff(parameter.C); }); } private void CopyStuff(ParamA parameter){ // Some long operation that will mainly wait on the disk } private void UpgradeStuff(ParamB parameter){ // Some long operation that should not block the GUI thread } public SomeResult ReloadStuff(ParamC parameter){ // Some long operation that relaunch some services and return their success } }
  1. في هذا النهج، نجعل الوظائف الفرعية async ونستخدم Task.Run داخلها:
csharp
public class Service : IService{ public async Task SomeMethod(SomeParameter parameter){ await CopyStuff(parameter.A); await UpgradeStuff(parameter.B); return await ReloadStuff(parameter.C); } private async Task CopyStuff(ParamA parameter){ await Task.Run(() => { /* Some long operation that will mainly wait on the disk */ }); } private async Task UpgradeStuff(ParamB parameter){ await Task.Run(() => { /* Some long operation that should not block the GUI thread */ }); } public async Task ReloadStuff(ParamC parameter){ return await Task.Run(() => { /* Some long operation that relaunch some services and return their success */ }); } }

لدينا مزايا لكل من النهجين:

  1. في النهج الأول، سنستخدم أقل عدد من المهام، مما قد يكون أكثر كفاءة.
  2. في النهج الثاني، يبدو هذا أكثر “انسجامًا” مع النهج await/async، كما أنه يسمح بتغيير رؤية بعض الوظائف ولا تزال تكون async. ويمكن أن يسمح هذا للوظائف بالتشغيل متزامنة إذا اقتضت الحاجة ذلك في يوم من الأيام.

لذا، الاختيار بين النهجين يعتمد على متطلبات تطبيقك الخاص والتوازن بين الأداء والانسجام مع الأساليب الموجودة.

المزيد من المعلومات

بما أن النقاط الرئيسية قد تم توضيحها، دعنا نستكشف بعض التفاصيل الإضافية لتوجيه قرارك.

أولاً، فيما يتعلق بالنهج الأول، حيث نستخدم Task.Run داخل الخدمة مباشرةً، يجب أن نكون حذرين من تبني هذا النهج بلا تفكير. استخدام Task.Run يعني أننا نأخذ عملية غير متزامنة ونقوم بتنفيذها في خيط منفصل، وهذا قد يكون مفيدًا في بعض الحالات ولكنه ليس دائمًا الحل الأمثل. فعند استخدام Task.Run، يتم إنشاء خيط جديد لتنفيذ العملية، وهذا يعني استهلاك المزيد من الموارد بما في ذلك الذاكرة ووقت المعالجة. بالإضافة إلى ذلك، إذا كانت العملية التي تنفذها Task.Run ليست تعيق الواجهة الرسومية (GUI)، فقد يؤدي استخدامها إلى تعقيد الشفرة دون فائدة ملحوظة.

أما بالنسبة للنهج الثاني، حيث نستخدم الوظائف الفرعية async وننتظر عليها باستخدام await، فيجب أن نكون حذرين من عدم إعطاء انطباع خاطئ بأن استخدام await يعني بالضرورة تنفيذ العملية في خيط منفصل. بالعكس، await يعني ببساطة أن الخيط الحالي يمكنه أن يتوقف وينتظر حتى اكتمال العملية دون حظر الواجهة الرسومية. وبالتالي، يمكن للوظائف الفرعية أن تنفذ في نفس الخيط الرئيسي دون الحاجة إلى إنشاء خيوط جديدة، مما يوفر استهلاك الموارد ويحسن أداء التطبيق.

في النهاية، القرار بين النهجين يعتمد على حالة استخدامك الخاصة. إذا كانت العملية التي تقوم بتنفيذها غير تعيق للواجهة الرسومية وتستغرق وقتًا طويلًا للانتظار (مثل عمليات الإدخال/الإخراج من القرص)، فقد يكون النهج الأول مناسبًا. أما إذا كانت العملية تعتبر خفيفة ولا تستهلك الوقت بشكل كبير، فيمكن أن يكون النهج الثاني الأكثر ملاءمة وانسجاماً مع الطريقة التقليدية لاستخدام await/async.

بغض النظر عن النهج الذي تختاره، يجب أن تكون هناك استراتيجية واضحة لمعالجة الأخطاء وإدارة الاستثناءات داخل الأساليب الفرعية. قد يتطلب هذا التفكير في كيفية تمرير الاستثناءات بين الوظائف الفرعية والأساسية، وكيفية التعامل معها بشكل فعال دون التسبب في انهيار التطبيق.

مقالات ذات صلة

زر الذهاب إلى الأعلى