مشاركة عبر


نموذج مؤشر الترابط

يتم تصميم Windows Presentation Foundation (WPF) لحفظ المطورين من صعوبات مؤشر الترابط.. كنتيجة معظم مطوري WPF لن يكونوا بحاجة لكتابة واجهة تستخدم أكثر من مؤشر ترابط واحد. لأن البرامج متعددة مؤشرات الترابط معقدة و صعبة التصحيح, فيجب تجنبها عند وجود حلول ترابط واحد.

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

يشتمل هذا الموضوع على الأقسام التالية.

  • نظرة عامة و المرسل
  • تشغيل مؤشرات الترابط: نماذج
  • تفاصيل فنية و نقاط Stumbling
  • موضوعات ذات صلة

نظرة عامة و المرسل

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

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

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

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

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

WPF لديه آلية استثناء متبادل مُضمنة التي تفرض هذا التنسيق. معظم الفئات في WPF يشتق من DispatcherObject.عند البناء, DispatcherObject يخزن مرجع إلى Dispatcher المربوط إلى مؤشر الترابط الحالي. في التأثير DispatcherObject يقرن مع مؤشر الترابط الذي يُنشئه. خلال تنفيذ البرنامج, DispatcherObject يمكن أن يستدعى أسلوب VerifyAccess العام. VerifyAccess يختبر Dispatcher المقترن بمؤشر الترابط الحالي و يقارنه مع مرجع Dispatcher المُخزن أثناء البناء. إذا لم يتوافقوا, VerifyAccess يطرح استثناء. VerifyAccess يقصد أن يستدعى في بداية كل أسلوب ينتمي إلى DispatcherObject.

إذا كان مؤشر ترابط واحد يمكنه تعديل واجهة المستخدم, كيف تتفاعل مؤشرات ترابط الخلفية مع المستخدم؟ مؤشر الترابط الخلفي يمكنه الطلب من مؤشر ترابط واجهة المستخدم أداء عملية بالنيابة عنه. يقوم بذلك عن طريق تسجيل عنصر عمل مع Dispatcher من مؤشر ترابط واجهة المستخدم. فئة Dispatcher توفر طريقتين لتسجيل عناصر العمل: Invoke و BeginInvoke. كلا الأسلوبين يقوموا بجدول زمني تفويض للتنفيذ. Invoke هو استدعاء متزامن -- هذا يعني أنه لا يرجع حتي ينتهي مؤشر ترابط واجهة المستخدم من تنفيذ التفويض. BeginInvoke هو غير متزامن و يرجع مباشرة.

يقوم Dispatcher بترتيب العناصر في قائمة الانتظار حسب الأولوية. توجد عشر مستويات قد يتم تحددها عند إضافة عنصر إلى قائمة الانتظار Dispatcher. يتم الحفاظ على هذه الأولويات في تعداد DispatcherPriority. معلومات مفصلة حول مستويات DispatcherPriority يمكن العثور عليها في وثائق Windows SDK.

تشغيل مؤشرات الترابط: نماذج

تطبيق مؤشر ترابط واحد مع عمليات حسابية مشغلة لفترة طويلة

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

الرجاء مراجعة المثال التالي:

لقطة شاشة لأرقام أولية

هذا التطبيق البسيط يعد إلى أعلى من رقم ثلاثة ، بحثاً عن الأعداد الأولية. عندما يقوم المستخدم بالنقر فوق زر ابدأ، يبدأ البحث. عند عثور البرنامج على رقم أولي، فإنه يقوم بتحديث واجهة المستخدم بالاكتشاف الخاص به. يمكن للمستخدم إيقاف البحث عند أي نقطة.

على الرغم من أنه بسيط, البحث عن الأرقام الأولية قد يكمل إلى ما لا نهاية و ذلك يقدم بعض المشكلات. إذا قمنا بمعالجة البحث بأكمله داخل معالج الأحداث النقر الخاص بالزر, لن نقوم أبداً بمنح مؤشر ترابط واجهة المستخدم الفرصة لمعالجة أحداث أخرى. واجهة المستخدم سيكون غير قادر على الاستجابة إلى الإدخال أو رسائل العملية. لن يعيد الرسم و لن يستجيب إلى النقر فوق الزر.

قد نقوم عقد البحث عن الأرقام الأولية في مؤشر ترابط منفصل, ولكن قد نحتاج للتعامل مع مشاكل التزامن. مع نهج مؤشر الترابط المفرد يمكننا مباشرة تحديث التسمية التي تسرد أكبر رقم أولي تم العثور عليه.

إذا قمنا بتقسيم مهمة عملية الحساب إلى قطع يمكن إدارتها, يمكننا أن الرجوع بشكل دوري Dispatcher و أحداث العملية. يمكن أن نعطي WPF الفرصة لإعادة الرسم و معالجة الإدخال.

أفضل طريقة لقسمة وقت المعالجة بين العملية الحسابية و معالجة الحدث هي بإدارة العملية الحسابية من Dispatcher. باستخدام أسلوب BeginInvoke, يمكن أن نقوم بجدولة تدقيقات الأرقام الأولية في نفس قائمة الانتظار التي أحداث واجهة المستخدم مأخوذة منها. في المثال الخاص بنا, نقوم بجدولة فحص رقم رئيسي واحد فقط في مرة الواحدة. بعد إتمام فحص الرقم الأولي نقوم بجدولة الفحص التالي فوراً. تستمر هذه العملية للفحص فقط بعد معالجة أحداث واجهة المستخدم المعلقة.

توضيح قائمة انتظار المرسل

برنامج Microsoft Word يحقق التدقيق الإملائي باستخدام هذه الآلية. يتم إجراء التدقيق الإملائي في الخلفية باستخدام وقت الخمول من مؤشر ترابط واجهة المستخدم. لنتعرف على التعليمات البرمجية.

يعرض المثال التالي XAML الذي ينشئ واجهة المستخدم.

<Window x:Class="SDKSamples.Window1"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    Title="Prime Numbers" Width="260" Height="75"
    >
  <StackPanel Orientation="Horizontal" VerticalAlignment="Center" >
    <Button Content="Start"  
            Click="StartOrStop"
            Name="startStopButton"
            Margin="5,0,5,0"
            />
    <TextBlock Margin="10,5,0,0">Biggest Prime Found:</TextBlock>
    <TextBlock Name="bigPrime" Margin="4,5,0,0">3</TextBlock>
  </StackPanel>
</Window>

يوضح المثال التالي التعليمات البرمجية الخلفية


Imports System
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Threading
Imports System.Threading

Namespace SDKSamples
    Partial Public Class Window1
        Inherits Window
        Public Delegate Sub NextPrimeDelegate()

        'Current number to check 
        Private num As Long = 3

        Private continueCalculating As Boolean = False

        Public Sub New()
            MyBase.New()
            InitializeComponent()
        End Sub

        Private Sub StartOrStop(ByVal sender As Object, ByVal e As EventArgs)
            If continueCalculating Then
                continueCalculating = False
                startStopButton.Content = "Resume"
            Else
                continueCalculating = True
                startStopButton.Content = "Stop"
                startStopButton.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New NextPrimeDelegate(AddressOf CheckNextNumber))
            End If
        End Sub

        Public Sub CheckNextNumber()
            ' Reset flag.
            NotAPrime = False

            For i As Long = 3 To CLng(Math.Sqrt(num))
                If num Mod i = 0 Then
                    ' Set not a prime flag to true.
                    NotAPrime = True
                    Exit For
                End If
            Next i

            ' If a prime number.
            If Not NotAPrime Then
                bigPrime.Text = num.ToString()
            End If

            num += 2
            If continueCalculating Then
                startStopButton.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.SystemIdle, New NextPrimeDelegate(AddressOf Me.CheckNextNumber))
            End If
        End Sub

        Private NotAPrime As Boolean = False
    End Class
End Namespace
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Threading;

namespace SDKSamples
{
    public partial class Window1 : Window
    {
        public delegate void NextPrimeDelegate();

        //Current number to check 
        private long num = 3;   

        private bool continueCalculating = false;

        public Window1() : base()
        {
            InitializeComponent();
        }

        private void StartOrStop(object sender, EventArgs e)
        {
            if (continueCalculating)
            {
                continueCalculating = false;
                startStopButton.Content = "Resume";
            }
            else
            {
                continueCalculating = true;
                startStopButton.Content = "Stop";
                startStopButton.Dispatcher.BeginInvoke(
                    DispatcherPriority.Normal,
                    new NextPrimeDelegate(CheckNextNumber));
            }
        }

        public void CheckNextNumber()
        {
            // Reset flag.
            NotAPrime = false;

            for (long i = 3; i <= Math.Sqrt(num); i++)
            {
                if (num % i == 0)
                {
                    // Set not a prime flag to true.
                    NotAPrime = true;
                    break;
                }
            }

            // If a prime number.
            if (!NotAPrime)
            {
                bigPrime.Text = num.ToString();
            }

            num += 2;
            if (continueCalculating)
            {
                startStopButton.Dispatcher.BeginInvoke(
                    System.Windows.Threading.DispatcherPriority.SystemIdle, 
                    new NextPrimeDelegate(this.CheckNextNumber));
            }
        }

        private bool NotAPrime = false;
    }
}

يظهر المثال التالي معالج الحدث لButton .

        Private Sub StartOrStop(ByVal sender As Object, ByVal e As EventArgs)
            If continueCalculating Then
                continueCalculating = False
                startStopButton.Content = "Resume"
            Else
                continueCalculating = True
                startStopButton.Content = "Stop"
                startStopButton.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New NextPrimeDelegate(AddressOf CheckNextNumber))
            End If
        End Sub
private void StartOrStop(object sender, EventArgs e)
{
    if (continueCalculating)
    {
        continueCalculating = false;
        startStopButton.Content = "Resume";
    }
    else
    {
        continueCalculating = true;
        startStopButton.Content = "Stop";
        startStopButton.Dispatcher.BeginInvoke(
            DispatcherPriority.Normal,
            new NextPrimeDelegate(CheckNextNumber));
    }
}

بالإضافة إلى تحديث النص على Button ، هذا المعالج مسؤول عن جدولة فحص اول رقم أولي بواسطة إضافة مفوض إلى قائمة الانتظار Dispatcher. أحياناً بعد إكمال معالج الحدث لعمله, Dispatcher سيختار هذا المفوض للتنفيذ.

كما ذكرنا سابقاً، BeginInvoke هو عضو Dispatcher المستخدم لجدولة المفوض للتنفيذ. في هذه الحالة، نقوم باختيار أولوية SystemIdle. Dispatcherسيقوم بتنفيذ هذا المفوض فقط عند وجود أية أحداث هامة إلى العملية. استجابة واجهة المستخدمهو أكثر أهمية من يتم الآن التدقيق من الأرقام. نقوم أيضا بتمرير مفوض جديد الذي يمثل في يتم الآن التدقيق من رقم روتين.

        Public Sub CheckNextNumber()
            ' Reset flag.
            NotAPrime = False

            For i As Long = 3 To CLng(Math.Sqrt(num))
                If num Mod i = 0 Then
                    ' Set not a prime flag to true.
                    NotAPrime = True
                    Exit For
                End If
            Next i

            ' If a prime number.
            If Not NotAPrime Then
                bigPrime.Text = num.ToString()
            End If

            num += 2
            If continueCalculating Then
                startStopButton.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.SystemIdle, New NextPrimeDelegate(AddressOf Me.CheckNextNumber))
            End If
        End Sub

        Private NotAPrime As Boolean = False
public void CheckNextNumber()
{
    // Reset flag.
    NotAPrime = false;

    for (long i = 3; i <= Math.Sqrt(num); i++)
    {
        if (num % i == 0)
        {
            // Set not a prime flag to true.
            NotAPrime = true;
            break;
        }
    }

    // If a prime number.
    if (!NotAPrime)
    {
        bigPrime.Text = num.ToString();
    }

    num += 2;
    if (continueCalculating)
    {
        startStopButton.Dispatcher.BeginInvoke(
            System.Windows.Threading.DispatcherPriority.SystemIdle, 
            new NextPrimeDelegate(this.CheckNextNumber));
    }
}

private bool NotAPrime = false;

هذا الأسلوب يتحقق مما إذا كان الرقم الفردي التالي أولي. إذا كان أولي , يقوم الأسلوب مباشرة بتحديث bigPrime TextBlock ليعكس اكتشافه. يمكن القيام بهذا لأن العملية الحسابية تحدث في نفس مؤشر الترابط الذي تم استخدامه في إنشاء المكون. إذا كنا قمنا باختيار استخدام مؤشر ترابط منفصل للعملية الحسابية, كان سيجب استخدام آلية متزامنة أكثر تعقيداً و تنفيذ التحديث في مؤشر ترابط واجهة المستخدم. سوف نقوم بشرح هذا الموقف التالي.

للحصول على المصدر الكامل للتعليمات البرمجية لهذه العينة ، راجع ‏‫تطبيق مؤشر ترابط واحد مع نموذج عمليات حسابية مشغلة لفترة طويلة

معالجة عملية حظر بمؤشر ترابط خلفي

يمكن أن تكون معالجة عمليات الحظر في تطبيق رسومي صعبة لا نريد استدعاء أساليب الحظر من معالجات الأحداث لأن التطبيق سيظهر أن يجمد. يمكن أن نستخدم مؤشر ترابط منفصل لمعالجة هذه العمليات و لكن عندما ننتهي, يجب أن نقوم بالمزامنة مع مؤشر ترابط واجهة المستخدم لأننا لا يمكن أن نقوم بتعديل GUI مباشرة من مؤشر ترابط العامل الخاص بنا. يمكننا استخدام Invoke أو BeginInvoke لإدراج التفويضات إلى Dispatcher من مؤشر ترابط واجهة المستخدم. أخيراً, تنفيذ المفوضين سيتم بإذن لتعديل عناصر واجهة المستخدم.

في هذا المثال، نقوم بتقليد استدعاء إجراء بعيد التي تقوم باسترداد تنبؤ الطقس. إننا نستخدم مؤشر ترابط العامل منفصل لتنفيذ هذا الاستدعاء و نقوم بجدولة أسلوب تحديث في Dispatcher من مؤشر ترابط واجهة المستخدم عند الانتهاء.

لقطة شاشة لواجهة مستخدم الطقس


Imports System
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Media
Imports System.Windows.Media.Animation
Imports System.Windows.Media.Imaging
Imports System.Windows.Shapes
Imports System.Windows.Threading
Imports System.Threading

Namespace SDKSamples
    Partial Public Class Window1
        Inherits Window
        ' Delegates to be used in placking jobs onto the Dispatcher.
        Private Delegate Sub NoArgDelegate()
        Private Delegate Sub OneArgDelegate(ByVal arg As String)

        ' Storyboards for the animations.
        Private showClockFaceStoryboard As Storyboard
        Private hideClockFaceStoryboard As Storyboard
        Private showWeatherImageStoryboard As Storyboard
        Private hideWeatherImageStoryboard As Storyboard

        Public Sub New()
            MyBase.New()
            InitializeComponent()
        End Sub

        Private Sub Window_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Load the storyboard resources.
            showClockFaceStoryboard = CType(Me.Resources("ShowClockFaceStoryboard"), Storyboard)
            hideClockFaceStoryboard = CType(Me.Resources("HideClockFaceStoryboard"), Storyboard)
            showWeatherImageStoryboard = CType(Me.Resources("ShowWeatherImageStoryboard"), Storyboard)
            hideWeatherImageStoryboard = CType(Me.Resources("HideWeatherImageStoryboard"), Storyboard)
        End Sub

        Private Sub ForecastButtonHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
            ' Change the status image and start the rotation animation.
            fetchButton.IsEnabled = False
            fetchButton.Content = "Contacting Server"
            weatherText.Text = ""
            hideWeatherImageStoryboard.Begin(Me)

            ' Start fetching the weather forecast asynchronously.
            Dim fetcher As New NoArgDelegate(AddressOf Me.FetchWeatherFromServer)

            fetcher.BeginInvoke(Nothing, Nothing)
        End Sub

        Private Sub FetchWeatherFromServer()
            ' Simulate the delay from network access.
            Thread.Sleep(4000)

            ' Tried and true method for weather forecasting - random numbers.
            Dim rand As New Random()
            Dim weather As String

            If rand.Next(2) = 0 Then
                weather = "rainy"
            Else
                weather = "sunny"
            End If

            ' Schedule the update function in the UI thread.
            tomorrowsWeather.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, New OneArgDelegate(AddressOf UpdateUserInterface), weather)
        End Sub

        Private Sub UpdateUserInterface(ByVal weather As String)
            'Set the weather image
            If weather = "sunny" Then
                weatherIndicatorImage.Source = CType(Me.Resources("SunnyImageSource"), ImageSource)
            ElseIf weather = "rainy" Then
                weatherIndicatorImage.Source = CType(Me.Resources("RainingImageSource"), ImageSource)
            End If

            'Stop clock animation
            showClockFaceStoryboard.Stop(Me)
            hideClockFaceStoryboard.Begin(Me)

            'Update UI text
            fetchButton.IsEnabled = True
            fetchButton.Content = "Fetch Forecast"
            weatherText.Text = weather
        End Sub

        Private Sub HideClockFaceStoryboard_Completed(ByVal sender As Object, ByVal args As EventArgs)
            showWeatherImageStoryboard.Begin(Me)
        End Sub

        Private Sub HideWeatherImageStoryboard_Completed(ByVal sender As Object, ByVal args As EventArgs)
            showClockFaceStoryboard.Begin(Me, True)
        End Sub
    End Class
End Namespace
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Threading;

namespace SDKSamples
{
    public partial class Window1 : Window
    {
        // Delegates to be used in placking jobs onto the Dispatcher.
        private delegate void NoArgDelegate();
        private delegate void OneArgDelegate(String arg);

        // Storyboards for the animations.
        private Storyboard showClockFaceStoryboard;
        private Storyboard hideClockFaceStoryboard;
        private Storyboard showWeatherImageStoryboard;
        private Storyboard hideWeatherImageStoryboard;

        public Window1(): base()
        {
            InitializeComponent();
        }  

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Load the storyboard resources.
            showClockFaceStoryboard = 
                (Storyboard)this.Resources["ShowClockFaceStoryboard"];
            hideClockFaceStoryboard = 
                (Storyboard)this.Resources["HideClockFaceStoryboard"];
            showWeatherImageStoryboard = 
                (Storyboard)this.Resources["ShowWeatherImageStoryboard"];
            hideWeatherImageStoryboard = 
                (Storyboard)this.Resources["HideWeatherImageStoryboard"];   
        }

        private void ForecastButtonHandler(object sender, RoutedEventArgs e)
        {
            // Change the status image and start the rotation animation.
            fetchButton.IsEnabled = false;
            fetchButton.Content = "Contacting Server";
            weatherText.Text = "";
            hideWeatherImageStoryboard.Begin(this);

            // Start fetching the weather forecast asynchronously.
            NoArgDelegate fetcher = new NoArgDelegate(
                this.FetchWeatherFromServer);

            fetcher.BeginInvoke(null, null);
        }

        private void FetchWeatherFromServer()
        {
            // Simulate the delay from network access.
            Thread.Sleep(4000);              

            // Tried and true method for weather forecasting - random numbers.
            Random rand = new Random();
            String weather;

            if (rand.Next(2) == 0)
            {
                weather = "rainy";
            }
            else
            {
                weather = "sunny";
            }

            // Schedule the update function in the UI thread.
            tomorrowsWeather.Dispatcher.BeginInvoke(
                System.Windows.Threading.DispatcherPriority.Normal,
                new OneArgDelegate(UpdateUserInterface), 
                weather);
        }

        private void UpdateUserInterface(String weather)
        {    
            //Set the weather image
            if (weather == "sunny")
            {       
                weatherIndicatorImage.Source = (ImageSource)this.Resources[
                    "SunnyImageSource"];
            }
            else if (weather == "rainy")
            {
                weatherIndicatorImage.Source = (ImageSource)this.Resources[
                    "RainingImageSource"];
            }

            //Stop clock animation
            showClockFaceStoryboard.Stop(this);
            hideClockFaceStoryboard.Begin(this);

            //Update UI text
            fetchButton.IsEnabled = true;
            fetchButton.Content = "Fetch Forecast";
            weatherText.Text = weather;     
        }

        private void HideClockFaceStoryboard_Completed(object sender,
            EventArgs args)
        {         
            showWeatherImageStoryboard.Begin(this);
        }

        private void HideWeatherImageStoryboard_Completed(object sender,
            EventArgs args)
        {           
            showClockFaceStoryboard.Begin(this, true);
        }        
    }
}

فيما يلي بعض التفاصيل التي يجب ملاحظتها.

  • إنشاء معالج الزر

            Private Sub ForecastButtonHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
                ' Change the status image and start the rotation animation.
                fetchButton.IsEnabled = False
                fetchButton.Content = "Contacting Server"
                weatherText.Text = ""
                hideWeatherImageStoryboard.Begin(Me)
    
                ' Start fetching the weather forecast asynchronously.
                Dim fetcher As New NoArgDelegate(AddressOf Me.FetchWeatherFromServer)
    
                fetcher.BeginInvoke(Nothing, Nothing)
            End Sub
    
    private void ForecastButtonHandler(object sender, RoutedEventArgs e)
    {
        // Change the status image and start the rotation animation.
        fetchButton.IsEnabled = false;
        fetchButton.Content = "Contacting Server";
        weatherText.Text = "";
        hideWeatherImageStoryboard.Begin(this);
    
        // Start fetching the weather forecast asynchronously.
        NoArgDelegate fetcher = new NoArgDelegate(
            this.FetchWeatherFromServer);
    
        fetcher.BeginInvoke(null, null);
    }
    

عندما يتم النقر فوق الزر, نقوم بعرض رسم الساعة و بدء الحركات عليه. نقوم بتعطيل الزر. نحن نستدعي أسلوب FetchWeatherFromServer في مؤشر ترابط جديد، ثم نرجع لنسمح إلى Dispatcher معالجة الأحداث بينما نقوم بالانتظار لتجميع حالة الجو.

  • جلب الطقس

            Private Sub FetchWeatherFromServer()
                ' Simulate the delay from network access.
                Thread.Sleep(4000)
    
                ' Tried and true method for weather forecasting - random numbers.
                Dim rand As New Random()
                Dim weather As String
    
                If rand.Next(2) = 0 Then
                    weather = "rainy"
                Else
                    weather = "sunny"
                End If
    
                ' Schedule the update function in the UI thread.
                tomorrowsWeather.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, New OneArgDelegate(AddressOf UpdateUserInterface), weather)
            End Sub
    
    private void FetchWeatherFromServer()
    {
        // Simulate the delay from network access.
        Thread.Sleep(4000);              
    
        // Tried and true method for weather forecasting - random numbers.
        Random rand = new Random();
        String weather;
    
        if (rand.Next(2) == 0)
        {
            weather = "rainy";
        }
        else
        {
            weather = "sunny";
        }
    
        // Schedule the update function in the UI thread.
        tomorrowsWeather.Dispatcher.BeginInvoke(
            System.Windows.Threading.DispatcherPriority.Normal,
            new OneArgDelegate(UpdateUserInterface), 
            weather);
    }
    

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

  • تحديث واجهة المستخدم

            Private Sub UpdateUserInterface(ByVal weather As String)
                'Set the weather image
                If weather = "sunny" Then
                    weatherIndicatorImage.Source = CType(Me.Resources("SunnyImageSource"), ImageSource)
                ElseIf weather = "rainy" Then
                    weatherIndicatorImage.Source = CType(Me.Resources("RainingImageSource"), ImageSource)
                End If
    
                'Stop clock animation
                showClockFaceStoryboard.Stop(Me)
                hideClockFaceStoryboard.Begin(Me)
    
                'Update UI text
                fetchButton.IsEnabled = True
                fetchButton.Content = "Fetch Forecast"
                weatherText.Text = weather
            End Sub
    
    private void UpdateUserInterface(String weather)
    {    
        //Set the weather image
        if (weather == "sunny")
        {       
            weatherIndicatorImage.Source = (ImageSource)this.Resources[
                "SunnyImageSource"];
        }
        else if (weather == "rainy")
        {
            weatherIndicatorImage.Source = (ImageSource)this.Resources[
                "RainingImageSource"];
        }
    
        //Stop clock animation
        showClockFaceStoryboard.Stop(this);
        hideClockFaceStoryboard.Begin(this);
    
        //Update UI text
        fetchButton.IsEnabled = true;
        fetchButton.Content = "Fetch Forecast";
        weatherText.Text = weather;     
    }
    

عندما يكون Dispatcher في مؤشر ترابط واجهة المستخدم لديه وقت, يقوم بتنفيذ استدعاء مجدول إلى UpdateUserInterface. هذا الأسلوب يوقف حركة الساعة و يختار صورة لوصف الطقس. يعرض هذه الصورة و يستعيد زر “ إحضار التنبؤ ”.

اطارت متعددة ،مؤشرات ترابط متعددة

بعض تطبيقات WPF تتطلب عدة إطارات ذات مستوى أعلى. مقبول تماماً لمؤشر ترابط واحد / مجموعة Dispatcher لإدارة الإطارات المتعددة ولكن في بعض الأحيان عدة مؤشرات ترابط تقوم بعمل أفضل. هذا صحيح خاصةً إذا كان هناك أي فرصة أن أحد الإطارات ستحتكر مؤشر الترابط.

مستكشف Windows يعمل بهذه الطريقة. ينتمي كل إطار مستكشف جديد إلى عملية أصلية لكن يتم إنشائه تحت تحكم مؤشر ترابط مستقل.

باستخدام عنصر تحكم WPF Frame، فإنه يمكن لنا عرض صفحات ويب. يمكننا بسهولة إنشاء بديل برنامج Internet Explorer بسيط. نبدأ بميزة هامة: القدرة على فتح إطار مستكشف جديد. عندما ينقر المستخدم فوق زر إطار جديد، نقوم بتشغيل نسخة من الإطار الخاص بنا في مؤشر ترابط منفصل. بهذه الطريقة، لن تقوم العمليات المشغلة لفترة طويلة أو عمليات الحظر في أحد الإطارات بتأمين كافة الإطارات الأخرى.

في الحقيقة, يحتوي طراز مستعرض الويب طراز مؤشر الترابط المعقد الخاص به. نحن اخترناها لأنه يجب أن تكون مألوفة لمعظم القراء.

يظهر المثال التالي التعليمات البرمجية.

<Window x:Class="SDKSamples.Window1"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    Title="MultiBrowse"
    Height="600" 
    Width="800"
    Loaded="OnLoaded"
    >
  <StackPanel Name="Stack" Orientation="Vertical">
    <StackPanel Orientation="Horizontal">
      <Button Content="New Window"
              Click="NewWindowHandler" />
      <TextBox Name="newLocation"
               Width="500" />
      <Button Content="GO!"
              Click="Browse" />
    </StackPanel>

    <Frame Name="placeHolder"
            Width="800"
            Height="550"></Frame>
  </StackPanel>
</Window>

Imports System
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Threading
Imports System.Threading


Namespace SDKSamples
    Partial Public Class Window1
        Inherits Window

        Public Sub New()
            MyBase.New()
            InitializeComponent()
        End Sub

        Private Sub OnLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
           placeHolder.Source = New Uri("https://www.msn.com")
        End Sub

        Private Sub Browse(ByVal sender As Object, ByVal e As RoutedEventArgs)
            placeHolder.Source = New Uri(newLocation.Text)
        End Sub

        Private Sub NewWindowHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim newWindowThread As New Thread(New ThreadStart(AddressOf ThreadStartingPoint))
            newWindowThread.SetApartmentState(ApartmentState.STA)
            newWindowThread.IsBackground = True
            newWindowThread.Start()
        End Sub

        Private Sub ThreadStartingPoint()
            Dim tempWindow As New Window1()
            tempWindow.Show()
            System.Windows.Threading.Dispatcher.Run()
        End Sub
    End Class
End Namespace
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Threading;
using System.Threading;


namespace SDKSamples
{
    public partial class Window1 : Window
    {

        public Window1() : base()
        {
            InitializeComponent();
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
           placeHolder.Source = new Uri("https://www.msn.com");
        }

        private void Browse(object sender, RoutedEventArgs e)
        {
            placeHolder.Source = new Uri(newLocation.Text);
        }

        private void NewWindowHandler(object sender, RoutedEventArgs e)
        {       
            Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
            newWindowThread.SetApartmentState(ApartmentState.STA);
            newWindowThread.IsBackground = true;
            newWindowThread.Start();
        }

        private void ThreadStartingPoint()
        {
            Window1 tempWindow = new Window1();
            tempWindow.Show();       
            System.Windows.Threading.Dispatcher.Run();
        }
    }
}

أجزاء مؤشر الترابط التالي لهذه التعليمات البرمجية هي المثيرة أكثر للاهتمام لنا في هذا السياق:

        Private Sub NewWindowHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
            Dim newWindowThread As New Thread(New ThreadStart(AddressOf ThreadStartingPoint))
            newWindowThread.SetApartmentState(ApartmentState.STA)
            newWindowThread.IsBackground = True
            newWindowThread.Start()
        End Sub
private void NewWindowHandler(object sender, RoutedEventArgs e)
{       
    Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
    newWindowThread.SetApartmentState(ApartmentState.STA);
    newWindowThread.IsBackground = true;
    newWindowThread.Start();
}

يتم استدعاء هذا الأسلوب عند النقر فوق زر “إطار جديد”. يقوم بإنشاء مؤشر ترابط جديد يبدأه بشكل غير متزامن.

        Private Sub ThreadStartingPoint()
            Dim tempWindow As New Window1()
            tempWindow.Show()
            System.Windows.Threading.Dispatcher.Run()
        End Sub
private void ThreadStartingPoint()
{
    Window1 tempWindow = new Window1();
    tempWindow.Show();       
    System.Windows.Threading.Dispatcher.Run();
}

هذا الأسلوب هو نقطة البداية لمؤشر الترابط الجديد. قمنا بإنشاء نافذة جديد ضمن عنصر تحكم لمؤشر الترابط هذا. WPFيقوم بإنشاء جديدDispatcherلإدارة مؤشر ترابط جديد. نحن فقط للقيام بتجهيز الإطار العمل هو لتبدأ Dispatcher.

تفاصيل فنية و نقاط Stumbling

استخدام مؤشر الترابط لكتابة المكونات

دليل مطوّري Microsoft NET Framewor. يوصف نقش لكيفية يمكن للمكون أن يعرض سلوك غير متزامن لعملائه (راجع تستند إلى الحدث نظرة عامة النقش غير متزامن). على سبيل المثال، افترض أننا نريد حزم أسلوب FetchWeatherFromServer إلى مكون قابل لإعادة الاستخدام و غير رسومي. متابعة نقش Microsoft NET Framewor. القياسي, سيبدو هذا كما يلي.

    Public Class WeatherComponent
        Inherits Component
        'gets weather: Synchronous 
        Public Function GetWeather() As String
            Dim weather As String = ""

            'predict the weather

            Return weather
        End Function

        'get weather: Asynchronous 
        Public Sub GetWeatherAsync()
            'get the weather
        End Sub

        Public Event GetWeatherCompleted As GetWeatherCompletedEventHandler
    End Class

    Public Class GetWeatherCompletedEventArgs
        Inherits AsyncCompletedEventArgs
        Public Sub New(ByVal [error] As Exception, ByVal canceled As Boolean, ByVal userState As Object, ByVal weather As String)
            MyBase.New([error], canceled, userState)
            _weather = weather
        End Sub

        Public ReadOnly Property Weather() As String
            Get
                Return _weather
            End Get
        End Property
        Private _weather As String
    End Class

    Public Delegate Sub GetWeatherCompletedEventHandler(ByVal sender As Object, ByVal e As GetWeatherCompletedEventArgs)
public class WeatherComponent : Component
{
    //gets weather: Synchronous 
    public string GetWeather()
    {
        string weather = "";

        //predict the weather

        return weather;
    }

    //get weather: Asynchronous 
    public void GetWeatherAsync()
    {
        //get the weather
    }

    public event GetWeatherCompletedEventHandler GetWeatherCompleted;
}

public class GetWeatherCompletedEventArgs : AsyncCompletedEventArgs
{
    public GetWeatherCompletedEventArgs(Exception error, bool canceled,
        object userState, string weather)
        :
        base(error, canceled, userState)
    {
        _weather = weather;
    }

    public string Weather
    {
        get { return _weather; }
    }
    private string _weather;
}

public delegate void GetWeatherCompletedEventHandler(object sender,
    GetWeatherCompletedEventArgs e);

GetWeatherAsync يستخدم إحدى التقنيات الموضحة في الإصدارات السابقة، مثل إنشاء مؤشر ترابط خلفي, للعمل بشكل غير متزامن، لا يحظر مؤشر الترابط المستدعي.

أحد الأجزاء الأكثر أهمية من هذا النقش هو استدعاء أسلوب MethodNameCompleted على نفس مؤشر الترابط الذي استدعى أسلوب MethodNameAsync لبدء به. قد تقوم بإجراء ذلك باستخدام WPF بسهولة عن طريق تخزين CurrentDispatcher — ولكن المكون غير الرسومي يمكن استخدامه فقط في تطبيقات WPF ، وليس في برامج Windows Forms أو ASP.NET.

فئة DispatcherSynchronizationContext تخاطب هذه الحاجة — فكر فيه كإصدار مبسط من Dispatcher الذي يعمل مع بعض إطارات واجهة المستخدم أيضاً.

    Public Class WeatherComponent2
        Inherits Component
        Public Function GetWeather() As String
            Return fetchWeatherFromServer()
        End Function

        Private requestingContext As DispatcherSynchronizationContext = Nothing

        Public Sub GetWeatherAsync()
            If requestingContext IsNot Nothing Then
                Throw New InvalidOperationException("This component can only handle 1 async request at a time")
            End If

            requestingContext = CType(DispatcherSynchronizationContext.Current, DispatcherSynchronizationContext)

            Dim fetcher As New NoArgDelegate(AddressOf Me.fetchWeatherFromServer)

            ' Launch thread
            fetcher.BeginInvoke(Nothing, Nothing)
        End Sub

        Private Sub [RaiseEvent](ByVal e As GetWeatherCompletedEventArgs)
            RaiseEvent GetWeatherCompleted(Me, e)
        End Sub

        Private Function fetchWeatherFromServer() As String
            ' do stuff
            Dim weather As String = ""

            Dim e As New GetWeatherCompletedEventArgs(Nothing, False, Nothing, weather)

            Dim callback As New SendOrPostCallback(AddressOf DoEvent)
            requestingContext.Post(callback, e)
            requestingContext = Nothing

            Return e.Weather
        End Function

        Private Sub DoEvent(ByVal e As Object)
            'do stuff
        End Sub

        Public Event GetWeatherCompleted As GetWeatherCompletedEventHandler
        Public Delegate Function NoArgDelegate() As String
    End Class
public class WeatherComponent2 : Component
{
    public string GetWeather()
    {
        return fetchWeatherFromServer();
    }

    private DispatcherSynchronizationContext requestingContext = null;

    public void GetWeatherAsync()
    {
        if (requestingContext != null)
            throw new InvalidOperationException("This component can only handle 1 async request at a time");

        requestingContext = (DispatcherSynchronizationContext)DispatcherSynchronizationContext.Current;

        NoArgDelegate fetcher = new NoArgDelegate(this.fetchWeatherFromServer);

        // Launch thread
        fetcher.BeginInvoke(null, null);
    }

    private void RaiseEvent(GetWeatherCompletedEventArgs e)
    {
        if (GetWeatherCompleted != null)
            GetWeatherCompleted(this, e);
    }

    private string fetchWeatherFromServer()
    {
        // do stuff
        string weather = "";

        GetWeatherCompletedEventArgs e =
            new GetWeatherCompletedEventArgs(null, false, null, weather);

        SendOrPostCallback callback = new SendOrPostCallback(DoEvent);
        requestingContext.Post(callback, e);
        requestingContext = null;

        return e.Weather;
    }

    private void DoEvent(object e)
    {
        //do stuff
    }

    public event GetWeatherCompletedEventHandler GetWeatherCompleted;
    public delegate string NoArgDelegate();
}

ضخ متداخل

في بعض الأحيان لا يكون مجدياً إغلاق مؤشر ترابط واجهة المستخدم بشكل كامل. لنأخذ بعين الاعتبار أسلوب Show من فئة MessageBox.Show لا يرجع إلى أن يقوم المستخدم بالنقر فوق زر "موافق". كذلك، فإنه على الرغم من ذلك، ينشئ إطار يجب أن يحتوي على تكرار حلقي للرسالة حتي يصبح تفاعلي. بينما تنتظر أن يقوم المستخدم بالنقر فوق زر موافق, لا يستجيب إطار التطبيق الأصلي إلى إدخال المستخدم. ومع ذلك، فإنه يتابع معالجة رسائل الرسم. الإطار الأصلي يقوم بإعادة رسم نفسه عند تغطيته أو الكشف عنه.

مربع رسالة بالزر "موافق"

يجب أن يكون مؤشر ترابط بعض المسؤول من صندوق الرسالة نافذة. قد WPFبإنشاء مؤشر ترابط جديد لإطار صندوق الرسالة فقط، ولكن مؤشر الترابط الحالي سجد إلى رسم العناصر المعطلة في الإطار الأصلي (تذكر المناقشة السابقة للاستبعاد المتبادلة). بدلاً من ذلك، WPFيستخدم النظام معالجة رسالة متداخلة.  Dispatcherتتضمن فئة خاص أسلوب يدعى PushFrame، التي sإلىres الإشارة التنفيذ الحالي الخاص بالتطبيق ثم بدء تكرار حلقي رسالة جديدة. عندما ينتهي تكرار حلقي رسالة متداخلة، يتم استئناف التنفيذ بعد الأصلي PushFrameالمكالمة.

في هذه الحالة، PushFrame يحتفظ بسياق البرنامج عند الاستدعاء إلى MessageBox.Show، و يبدأ تكرار حلقي للرسالة لإعادة رسم الإطار الخلفي و معالجة الإدخال إلى إطار مربع الرسالة. عند قيام المستخدم بالنقر فوق زر موافق و يمسح الإطار المنبثق, يتم إنهاء التكرار الحلقي المتداخل و عنصر التحكم يستئنف بعد الاستدعاء إلى Show.

الأحداث الموجهة التالفة

نظام الأحداث الموجهة في WPF يقوم بإعلام الأشجار بأكملها عند رفع الأحداث.

<Canvas MouseLeftButtonDown="handler1" 
        Width="100"
        Height="100"
        >
  <Ellipse Width="50"
           Height="50"
           Fill="Blue" 
           Canvas.Left="30"
           Canvas.Top="50" 
           MouseLeftButtonDown="handler2"
           />
</Canvas>

عند الضغط على زر الماوس الأيسر فوق القطع الناقص يتم تنفيذ handler2. بعد انتهاء handler2, يتم تمرير الحدث إلى كائن Canvas الذي يستخدم handler1 لمعالجته. يحدث هذا فقط إذا كان handler2 لا يُعلم بشكل صريح كائن الحدث كمُعالَج.

من الممكن أن handler2نافذة قدر كبير من وقت معالجة هذا حدث. handler2قد تستخدمPushFrameإلى بدء تكرار حلقي رسالة متداخلة يرجع لساعات. إذا كان handler2 لا يُعلِّم الحدث كمُعالَج عندما يكون هذا التكرار الحلقي للرسالة مكتمل, يتم تمرير الحدث أعلى الشجرة على الرغم من أنه قديم جداً.

إعادة الدخول و التأمين

آلية التأمين من وقت تشغيل اللغة العامة (CLR) لا تسلك تماماً السلوك المتخيل; قد تتوقع مؤشر ترابط لإيقاف العملية بالكامل عند طلب التأمين. في الحقيقة ، يستمر مؤشر الترابط في استقبال و معالجة الرسائل ذات أولوية عليا. هذا يساعد في منع حالات التوقف و جعل الواجهات ذو استجابة دنيا إلا أنه يقدم احتمالية الأخطاء الدقيقة. الغالبية العظمى من الوقت لا تحتاج إلى معرفة أي شيء حول هذا ولكن في حالات نادرة (عادةً ما تتضمن رسائل إطار Win32 أو مكونات COM STA) يمكن أن يكون يستحق المعرفة.

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

الرسم التخطيطي لإعادة دخول ترابط

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

إذا لماذا اختار فريق CLR هذا السلوك؟ انه له علاقة بكائنات COM STA و مؤشر الترابط النهائي. عندما يتم تجميع الكائن كبيانات مهملة, يتم تشغيل أسلوب Finalize الخاص به على مؤشر ترابط الإنهاء المخصص و ليس مؤشر ترابط واجهة المستخدم.و هنا تكمن مشكلة لأن كائن COM STA الذي تم انشاءه على مؤشر ترابط واجهة المستخدم يمكن التخلص منه فقط من على مؤشر ترابط واجهة المستخدم. CLR يفعل المكافئ إلى BeginInvoke (في حالة استخدام SendMessage الخاص بWin32). لكن اذا كان مؤشر ترابط واجهة المستخدم مشغول يتم تثبيت موشر ترابط الانهاء و لا يمكن التخلص من كائن COM STA الذي ينشئ تسرب للذاكرة جدي. لذلك قام فريق CLR باختيار صعب لجعل عمليات التأمين تعمل مثلما يريدون.

مهمة WPF هي تجنب اعادة الدخول غير المتوقع دون اعادة تعريف تسرب الذاكرة الذي هو السبب أننا لا نقوم بحظر اعادة الدخول كل مكان.

راجع أيضًا:

موارد أخرى

تطبيق مؤشر ترابط واحد مع نموذج عمليات حسابية مشغلة لفترة طويلة