كيف نختار بين PeriodicTimer و System.Threading.Timer و DispatcherTimer - تنظيم العمل الدوريّ في .NET أوّلاً
· 小村 豪 · C#, .NET, WPF, Timer, Design
في المقال السابق دليل عمليّ للزمن الناعم على Windows - قائمة تحقّق لخفض الـ latency، نظّمتُ كيفيّة تجنّب الحلقات الدوريّة المعتمدة على Sleep، ومتى يجب التفكير بأسلوب التصميم المدفوع بالأحداث أو بـ waitable timers.
ولكن، ماذا عن تطوير تطبيقات .NET الاعتياديّ؟
الثلاثيّ الذي يُربك عادةً هناك هو PeriodicTimer و System.Threading.Timer و DispatcherTimer.
كلّها تُسمّى “مؤقّتات”، لكنّها مختلفة جدّاً:
- مؤقّت يسمح لك بانتظار الـ ticks بـ
await - مؤقّت يُطلق callbacks على الـ ThreadPool
- مؤقّت يعمل على
Dispatcherالخاصّ بـ UI thread
أنواع الأخطاء التي تظهر في المشاريع الواقعيّة هي عادةً:
- وضع
asynclambda داخلSystem.Threading.Timerرغم أنّ العمل الفعليّ غير متزامن - تحديث واجهة WPF مباشرةً من callback لمؤقّت ThreadPool
- وضع عمل ثقيل في
DispatcherTimerوإبطاء الواجهة بأكملها - خلط “العمل الدوريّ الاعتياديّ للتطبيق” في هذا المقال ذهنيّاً مع “دقّة التوقيت في الزمن الناعم” من المقال السابق
يفترض هذا المقال في الغالب تطبيقات C# / .NET اعتياديّة على .NET 6 وما بعد، ويُنظّم PeriodicTimer و System.Threading.Timer و DispatcherTimer بترتيب عمليّ.
تشمل الأهداف النموذجيّة:
- workers و background services
- تطبيقات الـ console
- المهامّ الخلفيّة على جانب الخادم في ASP.NET Core
- تطبيقات WPF لسطح المكتب
عندما أقول DispatcherTimer هنا، أعني أساساً System.Windows.Threading.DispatcherTimer الخاصّ بـ WPF.
يحمل WinUI / UWP فكرةً مماثلة.
أمّا في WinForms، فإنّ System.Windows.Forms.Timer هو عادةً مؤقّت الواجهة الأكثر طبيعيّةً للنظر فيه.
كذلك، يدور هذا المقال حول كيفيّة كتابة العمل الدوريّ على جانب التطبيق.
إذا كان الموضوع الحقيقيّ هو دقّة التوقيت بحدّ ذاتها، فإنّ ذلك يعود إلى مقال الزمن الناعم السابق.
المحتويات
- النسخة المختصرة
- أوّلاً: تنظيم الأمر في صفحة واحدة
- التمييزات الواجب إجراؤها أوّلاً
- الأنماط النموذجيّة
- أنماط مضادّة شائعة
- قائمة تحقّق للمراجعة
- دليل تقريبيّ كقواعد إبهام
- الخلاصة
- مراجع
1. النسخة المختصرة
- إذا أردت كتابة عمل بفترات ثابتة بأسلوب طبيعيّ مبنيّ على
await، فابدأ بـPeriodicTimer - إذا أردت إطلاق callbacks خفيفة على الـ ThreadPool في فترات منتظمة، فاستخدم
System.Threading.Timer - إذا أردت تحديثات دوريّة للواجهة على UI thread الخاصّ بـ WPF، فاستخدم
DispatcherTimer - callbacks الـ
System.Threading.Timerيمكن أن تتداخل؛ إن دفعت إليه عملاً غير متزامن باستخفاف، تتفوّض الأمور بسرعة - يسمح لك
DispatcherTimerبلمس الواجهة مباشرةً، لكنّ العمل الثقيل هناك يمكن أن يبطئ الواجهة بأكملها - بمعنى الزمن الناعم في المقال السابق، لا ينبغي معاملة أيّ من هذه الثلاثة على أنّها الأداة الرئيسيّة للانتظار عالي الدقّة
الأسئلة الثلاثة الواجب فصلها أوّلاً هي:
- أيّ thread أو context يجب أن يُشغّل هذا العمل؟
- هل تريد أن يُقرأ جسم المعالجة بشكل طبيعيّ بأسلوب
async/await؟ - هل يمكن تحمّل تداخل الـ callbacks؟
مجرّد فصل هذه الأسئلة يجعل الاختيار أسهل بكثير.
2. أوّلاً: تنظيم الأمر في صفحة واحدة
2.1. الصورة الإجماليّة
flowchart LR
A["You want to do something periodically"] --> B{"Should it run on the UI thread?"}
B -- "Yes" --> C["DispatcherTimer"]
B -- "No" --> D{"Do you want the body to read naturally as<br/>async / await?"}
D -- "Yes" --> E["PeriodicTimer"]
D -- "No" --> F{"Do you want a light callback<br/>on the ThreadPool?"}
F -- "Yes" --> G["System.Threading.Timer"]
F -- "No" --> H["Consider a different design<br/>Channel / BackgroundService / events / waitable timer"]
في العمل اليوميّ، يكفي هذا التفرّع عادةً.
إذا أردت أكثر تخمين أوّل أماناً:
- للعمل الدوريّ غير المتزامن، ابدأ بـ
PeriodicTimer - لتحديثات الواجهة، ابدأ بـ
DispatcherTimer
System.Threading.Timer مفيد، لكنّه يحمل خصائص أكثر حول تداخل الـ callbacks والـ lifetime.
إنّه أكثر مزاجيّةً قليلاً بوصفه “أوّل مؤقّت”.
2.2. أوّل جدول قرار
| الموقف | الخيار الأوّل | المكان الذي يعمل فيه | لماذا يلائم | أوّل تحذير |
|---|---|---|---|---|
| عمل I/O غير متزامن دوريّ مثل HTTP / DB / عمل الملفّات | PeriodicTimer |
داخل الـ async flow الحاليّ | يُقرأ الكود طبيعيّاً مع await، والإلغاء واضح |
افترض مؤقّت واحد / مستهلك واحد؛ لا يُوازن العمل المتأخّر تلقائيّاً |
| heartbeat / metrics / فحوصات انتهاء صلاحيّة الـ cache الخفيفة | System.Threading.Timer |
ThreadPool | نموذج callback خفيف الوزن، يسهل ربطه بتصاميم قائمة على الـ callbacks | الـ callbacks قابلة للدخول مجدّداً عمليّاً؛ يمكن أن تتداخل؛ احتفظ بمرجع |
| تحديثات واجهة WPF دوريّة مثل ساعة أو عرض حالة | DispatcherTimer |
Dispatcher الخاصّ بـ WPF (UI thread) |
يمكنك لمس الواجهة مباشرةً، والأولويّة جزء من النموذج | لا ضمان لتوقيت إطلاق دقيق؛ العمل الثقيل يحجب الواجهة |
| مشكلة تكون فيها دقّة التوقيت بحدّ ذاتها هي الجوهر | لا تجعل هذه الثلاثة الأداة الرئيسيّة | - | تصبح المشكلة عن استراتيجيّة الانتظار بدلاً من اختيار مؤقّت على جانب التطبيق | عُد إلى تصميم event / waitable timer / scheduling |
أهمّ شيء في هذا الجدول هو أنّ اسم المؤقّت يهمّ أقلّ من سياق التنفيذ ونموذج المعالجة. حين تختار الفرق بشكل سيّئ هنا، يكون الخطأ غالباً ليس في “اسم API الخاطئ”، بل في عدم النظر إلى أين يعمل الكود.
3. التمييزات الواجب إجراؤها أوّلاً
3.1. أسلوب callback مقابل أسلوب انتظار الـ tick
هذا التمييز وحده يُزيل كثيراً من الإرباك.
System.Threading.TimerوDispatcherTimerهما بأسلوب callback / eventPeriodicTimerبأسلوب انتظار الـ tick عبرawait
إذاً:
- مؤقّتات الـ callback تعني “المؤقّت يستدعيك”
PeriodicTimerيعني “أنت تنتظر الـ tick التالي”
إذا كان العمل الفعليّ غير متزامن بالفعل وأردت قراءته على شكل:
- انتظر
- نفّذ العمل
- انتظر مرّةً أخرى
عندئذٍ يكون PeriodicTimer عادةً الأنسب.
في المقابل، إذا:
- كان التصميم بالفعل قائماً على callbacks
- كان العمل قصيراً ومتزامناً
- أردت فقط بدءاً دوريّاً
عندئذٍ يلائم System.Threading.Timer بشكل أكثر طبيعيّة.
PeriodicTimer مفيد، لكنّه ليس سحريّاً.
لم يُصمَّم حول مستهلكين متزامنين متعدّدين ينتظرون نفس المؤقّت، ويمكن أن تنطوي ticks تحدث بينما لا تنتظر إلى حدث واحد عمليّاً.
3.2. التنفيذ على ThreadPool مقابل التنفيذ على UI thread
السؤال التالي هو أين يعمل الكود فعلاً.
تعمل callbacks الـ System.Threading.Timer على الـ ThreadPool، لا على الـ thread الذي أنشأ المؤقّت.
يجعله ذلك ملائماً للعمل الخلفيّ، لكن ليس للتلاعب المباشر بالواجهة.
في المقابل، يعمل DispatcherTimer على Dispatcher الخاصّ بـ WPF.
يعني هذا أنّ معالج Tick الخاصّ به يستطيع لمس الواجهة مباشرةً.
هذا تمييز كبير.
- يحتاج مؤقّت ThreadPool إلى marshaling صريح إذا أردت تحديث الواجهة
DispatcherTimerملائم لعمل الواجهة، لكن ذلك يعني أيضاً أنّ عمله يستهلك وقت UI thread
لذا فإنّ كون DispatcherTimer “آمناً للوصول المباشر إلى الواجهة” هو قوّته ومخاطرته معاً.
3.3. العمل الدوريّ وضمانات التوقيت مشكلتان مختلفتان
هذه أهمّ صلة بمقال الزمن الناعم السابق.
عبارة “افعل شيئاً دوريّاً” يمكن أن تعني مشكلات مختلفة جدّاً:
- “نفّذ هذا كلّ بضع ثوانٍ كمهمّة تطبيق”
- “نفّذ هذا كلّ 1 ms بأقلّ jitter ممكن”
System.Threading.Timer خفيف الوزن وعمليّ، لكنّه ليس أداةً متخصّصةً في دقّة التوقيت.
يتأثّر DispatcherTimer أيضاً بطابور الواجهة وأولويّة الـ Dispatcher.
يبدو PeriodicTimer دقيقاً من اسمه، لكنّ قوّته الحقيقيّة هي مدى طبيعيّة كتابة async loop به، لا ضمانات التوقيت الصارمة.
لذا من الأكثر أماناً فصل:
- العمل الدوريّ على جانب التطبيق
- دقّة التوقيت بوصفها مشكلة زمن حقيقيّ
إن اختلطت هاتان معاً، تصبح المناقشات حول المؤقّتات مشوّشةً بسرعة.
4. الأنماط النموذجيّة
4.1. للعمل الدوريّ غير المتزامن، استخدم PeriodicTimer
إذا أردت عملاً دوريّاً غير متزامن داخل worker أو BackgroundService أو حلقة مقيمة مماثلة، يكون PeriodicTimer عادةً الأسهل قراءةً.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
public sealed class CacheRefreshWorker : BackgroundService
{
private readonly ILogger<CacheRefreshWorker> _logger;
public CacheRefreshWorker(ILogger<CacheRefreshWorker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("CacheRefreshWorker started.");
await RefreshCacheAsync(stoppingToken);
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
await RefreshCacheAsync(stoppingToken);
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("CacheRefreshWorker stopping.");
}
}
private async Task RefreshCacheAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Refreshing cache...");
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
}
هذا الشكل لطيف لأنّ:
- تدفّق التحكّم سهل المتابعة كميثود
asyncواحد - يسهل تمرير
CancellationTokenنزولاً - تتجنّب الكثير من ضوضاء lifetime الـ callback ومعالجة الاستثناءات
إنّه مريح بشكل خاصّ حين يكون الجسم في الغالب مرتبطاً بـ I/O:
- استدعاء HTTP
- استعلام قاعدة بيانات
- قراءة الملفّات
- انتظار APIs غير متزامنة أخرى
تحذيران مهمّان جدّاً:
- افترض مؤقّت واحد / مستهلك واحد
- قرّر بشكل صريح ما يجب فعله إذا استغرق العمل وقتاً أطول من الفترة
لا يوازن PeriodicTimer تلقائيّاً العمل المتأخّر بهدف “اللحاق”.
إنّه أداة لكتابة async periodic loop واضح، لا scheduler يصحّح تصميمك نيابةً عنك.
إذا كانت قابليّة الاختبار مهمّة، فإنّ overloads المُنشِئ التي تتكامل مع TimeProvider مفيدة بهدوء أيضاً.
4.2. لـ callbacks خفيفة على ThreadPool، استخدم System.Threading.Timer
إذا أردت فقط إطلاق callback قصير دوريّاً، فإنّ System.Threading.Timer مباشر.
الحالات النموذجيّة:
- إرسال heartbeat
- التقاط metrics خفيفة
- فحوصات سريعة لانتهاء صلاحيّة الـ cache
- ربط trigger دوريّ صغير بتصميم قائم على الـ callbacks
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
public sealed class HeartbeatService : IHostedService, IDisposable
{
private readonly ILogger<HeartbeatService> _logger;
private Timer? _timer;
private int _running;
public HeartbeatService(ILogger<HeartbeatService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(OnTimer, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void OnTimer(object? state)
{
if (Interlocked.Exchange(ref _running, 1) != 0)
{
return;
}
try
{
_logger.LogInformation("Heartbeat: {Now}", DateTimeOffset.Now);
}
finally
{
Volatile.Write(ref _running, 0);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
السبب في استخدام المثال لـ Interlocked.Exchange مهمّ:
لا ينتظر System.Threading.Timer انتهاء الـ callback السابق.
إذاً:
- تعمل الـ callbacks على ThreadPool
- التداخل ممكن
- إذا كان العمل أطول من الفترة، يمكن أن تتراكم الـ callbacks
إذا لم يكن العمل تافهاً، يكون من الأفضل عادةً:
- تجاوز triggers المكرّرة
- وضع العمل في طابور في مكان آخر
- أو التحوّل إلى
PeriodicTimer
نقطة عمليّة أخرى هي الاحتفاظ بمرجع.
حتّى أثناء كونه نشطاً، يمكن أن يصبح System.Threading.Timer قابلاً للجمع إن فقدت كلّ المراجع إليه.
كذلك، بعد استدعاء Dispose()، يمكن أن يعمل callback كان قد أُدرج في الطابور لاحقاً.
إذاً System.Threading.Timer:
- خفيف الوزن
- سريع
- بسيط
لكن فقط إن كنت مستعدّاً لامتلاك سلوك الـ callback بشكل صحيح.
4.3. لتحديثات واجهة WPF، استخدم DispatcherTimer
إذا أردت تحديث ساعة أو عرض حالة خفيف على شاشة WPF، يكون DispatcherTimer الخيار الطبيعيّ.
using System;
using System.Windows;
using System.Windows.Threading;
public partial class MainWindow : Window
{
private readonly DispatcherTimer _clockTimer;
public MainWindow()
{
InitializeComponent();
_clockTimer = new DispatcherTimer(DispatcherPriority.Background)
{
Interval = TimeSpan.FromSeconds(1)
};
_clockTimer.Tick += ClockTimer_Tick;
_clockTimer.Start();
}
private void ClockTimer_Tick(object? sender, EventArgs e)
{
ClockText.Text = DateTime.Now.ToString("HH:mm:ss");
}
protected override void OnClosed(EventArgs e)
{
_clockTimer.Stop();
_clockTimer.Tick -= ClockTimer_Tick;
base.OnClosed(e);
}
}
الجزء الجيّد هو أنّ Tick يعمل على Dispatcher الخاصّ بـ WPF، لذا فإنّ تحديثات الواجهة مباشرة.
يلائم ذلك سيناريوهات مثل:
- ساعة
- عرض حالة اتّصال خفيف
- triggers لإعادة تقييم الأوامر
- التحديث الدوريّ لقيم موجودة بالفعل على الشاشة
لكنّ المقايضة تتغيّر معه:
لأنّ DispatcherTimer يعمل على UI thread، فإنّ أيّ عمل ثقيل في Tick يتنافس مباشرةً مع الإدخال والـ layout والعرض.
كذلك، DispatcherTimer ليس أداة “إطلاق في الوقت المطلوب بالضبط”.
إنّه يتأثّر بطابور Dispatcher والأولويّة.
لذا في الممارسة:
- اجعل معالجات Tick خفيفة
- انقل عمل I/O أو الـ CPU الثقيل إلى مكان آخر
- أوقف المؤقّت بشكل صريح وألغِ الاشتراك عند الإغلاق
4.4. للمعالجة الدوريّة بأسلوب الزمن الناعم، انظر إلى أدوات مختلفة
هذه هي الصلة بالمقال السابق.
ذلك المقال لم يكن عن “كلّ بضع ثوانٍ تقريباً جيّد بما فيه الكفاية”. كان عن كيفيّة خفض الـ jitter وتفويت الـ deadlines.
في ذلك السياق، الموضوعات المهمّة هي أمور مثل:
- تجنّب الانتظار النسبيّ المعتمد على
Sleep - استخدام أسلوب الانتظار المدفوع بالأحداث أو waitable timer
- فصل fast path و slow path
- قياس التأخّر بشكل صريح
لذا من الأنظف فصل المساحة الإشكاليّة هكذا:
- العمل الدوريّ غير المتزامن الاعتياديّ على جانب التطبيق
←PeriodicTimer - callbacks ThreadPool خفيفة الوزن
←System.Threading.Timer - تحديثات الواجهة
←DispatcherTimer - دقّة التوقيت بحدّ ذاتها كمشكلة رئيسيّة
← عُد إلى مناقشة الزمن الناعم
بمجرّد أن يصبح السؤال:
“أيّ مؤقّت في .NET يجب أن أستخدم إن أردت العمل كلّ 1 ms بأكبر دقّة ممكنة؟”
فإنّك لم تعد فعلاً تختار بين مؤقّتات على جانب التطبيق. إنّك تصمّم استراتيجيّة الانتظار وسلوك النظام.
5. أنماط مضادّة شائعة
5.1. تمرير async lambda مباشرةً إلى System.Threading.Timer
هذا مغرٍ جدّاً:
_timer = new Timer(async _ => await RefreshAsync(), null,
TimeSpan.Zero, TimeSpan.FromSeconds(5));
يبدو الأمر أنيقاً، لكنّ TimerCallback هو void.
يعني هذا أنّ الـ async lambda هو عمليّاً في منطقة async void.
عندئذٍ:
- لا يمكن للمستدعي انتظاره
- لا يمكن تنسيق الإكمال بنظافة
- تصبح معالجة الاستثناءات أصعب
- ولا يزال تداخل الـ callbacks مشكلةً منفصلة
إذا كان الجسم غير متزامن فعلاً، فإنّ PeriodicTimer غالباً ما يكون أوّل ما يجب التفكير فيه بدلاً من ذلك.
5.2. وضع عمل ثقيل في DispatcherTimer.Tick
لأنّ DispatcherTimer يستطيع لمس الواجهة مباشرةً، يسهل الاستمرار في إضافة عمل إلى Tick.
لكن ذلك هو UI thread.
العمل المتزامن الثقيل أو I/O الحاجب أو المنطق غير المتزامن المعرّض للتداخل هناك يمكن أن يضرّ مباشرةً بالإدخال والعرض.
5.3. افتراض أنّ PeriodicTimer يلحق تلقائيّاً عند التأخّر
هذا سوء فهم شائع آخر.
PeriodicTimer ممتاز للتعبير عن async periodic loop، لكنّه لا يوازن العمل المتأخّر تلقائيّاً ولا يضمن أنّ كلّ فترة فائتة يتمّ إعادة تشغيلها على حدة.
لذا لا يزال عليك أن تقرّر:
- هل ينبغي تجاوز التكرارات المتأخّرة؟
- هل آخر حالة فقط هي المهمّة؟
- هل تحتاج فعلاً إلى معالجة كلّ tick؟
5.4. تأجيل إدارة الإيقاف والـ lifetime
تكون المؤقّتات في الغالب أسهل في البدء منها في الإيقاف بنظافة.
تشمل الأخطاء النموذجيّة:
- إنشاء
System.Threading.Timerدون الاحتفاظ بمرجع - استدعاء Dispose على مؤقّت دون فهم أنّ callbacks مُدرجة في الطابور قد تعمل لاحقاً
- نسيان استدعاء
Stop()أو إلغاء الاشتراك فيDispatcherTimer
6. قائمة تحقّق للمراجعة
عند مراجعة المعالجة الدوريّة، تحقّق من هذه بالترتيب:
- هل المشكلة الفعليّة هي تحديثات الواجهة، أم العمل الدوريّ غير المتزامن، أم triggering بأسلوب الـ callback؟
- هل يطابق المؤقّت المختار سياق التنفيذ؟
- هل يمكن أن تتداخل الـ callbacks، وإن لم يكن، فأين يُمنع ذلك؟
- هل الـ lifetime صريح والإيقاف نظيف؟
- هل يستخدم الكود عن غير قصد مؤقّتاً على جانب التطبيق لمشكلة دقّة التوقيت؟
7. دليل تقريبيّ كقواعد إبهام
| ما تريد فعله | أوّل شيء يجب اختياره |
|---|---|
| كتابة عمل دوريّ غير متزامن طبيعيّ | PeriodicTimer |
| إطلاق callbacks دوريّة خفيفة على الـ ThreadPool | System.Threading.Timer |
| تحديث واجهة WPF دوريّاً | DispatcherTimer |
| التحكّم بطابور خلفيّ مرتّب | Channel<T> + worker |
| ملاحقة دقّة التوقيت بحدّ ذاتها | تصميم مدفوع بالأحداث / waitable timer / تصميم scheduler |
8. الخلاصة
يصبح الاختيار بين PeriodicTimer و System.Threading.Timer و DispatcherTimer أسهل بكثير بمجرّد أن تسأل:
- أين يجب أن يعمل هذا العمل؟
- هل أريد أن يُقرأ الجسم بوصفه تدفّقاً غير متزامن طبيعيّاً؟
- هل يمكن تحمّل تداخل الـ callbacks؟
بمجرّد أن تتّضح هذه الإجابات، يكون اختيار المؤقّت بحدّ ذاته مباشراً عادةً.
9. مراجع
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
لماذا يستحقّ الأمر إدخال Generic Host / BackgroundService إلى تطبيق سطح المكتب - يصبح تنظيم البدء والـ lifetime والـ graceful shutdown أسهل بكثير
يشرح هذا المقال متى يستحقّ إدخال Generic Host و BackgroundService إلى تطبيقات سطح المكتب على Windows لتنظيم البدء وإدارة الـ lifetime وال...
async/await و UI thread في WPF / WinForms في صفحة واحدة - أين تعود الاستكمالات، و Dispatcher، و ConfigureAwait، ولماذا تتعطّل .Result / .Wait()
شرح عمليّ موجز للعلاقة بين async / await و UI thread في WPF و WinForms، يوضّح أين تستأنف الاستكمالات، ودور Dispatcher و ConfigureAwait، و...
إلى أين ينتهي unit test وأين يبدأ integration test - دليل عمليّ لرسم الحدّ الفاصل
دليل عمليّ يميّز unit test وintegration test بأربعة أسئلة: نتحقّق من منطقنا أم من الغراء، ويبقى المعنى مع fake، وما طبيعة الاعتماد، ومدى ...
قائمة تحقّق للتعامل الآمن مع child processes في تطبيقات Windows - Job Objects ونشر الـ exit وstdio وتصميم الـ watchdog
دليل تصميم متكامل للتعامل الآمن مع child processes في تطبيقات Windows عبر Job Objects ونشر الـ exit وتصريف stdio ووضع الـ watchdog خارج ا...
مزالق تطبيقات serial communication - framing وtimeouts وflow control وreconnects ومحوّلات USB وتجمّد الـ UI
ملخّص عمليّ لمزالق تطبيقات serial communication على Windows: framing وtimeouts وflow control وreconnects ومحوّلات USB-to-serial وتجمّد ال...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
ندعم تطوير برامج ويندوز للأعمال، وتكامل الأجهزة، وأدوات التواصل.
الاستشارات التقنية ومراجعة التصميم
استشارة تقنية لترتيب خطط التعديل، ومراجعة التصميم، والتعامل مع الأصول القديمة.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.
روابط عامة