Модель потоковThreading Model

Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) призвана помочь разработчикам избежать трудностей при разработке потоков.is designed to save developers from the difficulties of threading. Как следствие, большинство WPFWPF разработчикам не требуется писать интерфейс, использующий более одного потока.As a result, the majority of WPFWPF developers won't have to write an interface that uses more than one thread. Поскольку многопотоковые программы являются сложными и трудно отлаживаемыми, их следует избегать, если существуют однопоточные решения.Because multithreaded programs are complex and difficult to debug, they should be avoided when single-threaded solutions exist.

Независимо от того, насколько хорошо качества архитектуры, нет ИПUI framework никогда не будут иметь возможность предоставить однопоточное решение для каждого типа задач.No matter how well architected, however, no ИПUI framework will ever be able to provide a single-threaded solution for every sort of problem. WPFWPF приблизилось, но по-прежнему существуют ситуации, в которых несколько потоков улучшают пользовательский интерфейсuser interface (UI) скорость реагирования или производительность приложения.comes close, but there are still situations where multiple threads improve пользовательский интерфейсuser interface (UI) responsiveness or application performance. После рассмотрения некоторых основных материалов в данном документе рассматриваются подобные ситуации и в завершение обсуждаются некоторые более подробные сведения.After discussing some background material, this paper explores some of these situations and then concludes with a discussion of some lower-level details.

Примечание

В этом разделе обсуждается создание потоков с помощью BeginInvoke метод для асинхронных вызовов.This topic discusses threading by using the BeginInvoke method for asynchronous calls. Вы также можете асинхронных вызовов, вызвав InvokeAsync метод, который занять Action или Func<TResult> как параметр.You can also make asynchronous calls by calling the InvokeAsync method, which take an Action or Func<TResult> as a parameter. InvokeAsync Возвращает DispatcherOperation или DispatcherOperation<TResult>, который имеет Task свойства.The InvokeAsync method returns a DispatcherOperation or DispatcherOperation<TResult>, which has a Task property. Можно использовать await ключевого слова with либо DispatcherOperation или связанного Task.You can use the await keyword with either the DispatcherOperation or the associated Task. Если требуется синхронно дождаться Task , возвращаемый DispatcherOperation или DispatcherOperation<TResult>, вызовите DispatcherOperationWait метода расширения.If you need to wait synchronously for the Task that is returned by a DispatcherOperation or DispatcherOperation<TResult>, call the DispatcherOperationWait extension method. Вызов Task.Wait приведет к взаимоблокировке.Calling Task.Wait will result in a deadlock. Дополнительные сведения об использовании Task для выполнения асинхронных операций, см. в разделе параллелизм задач.For more information about using a Task to perform asynchronous operations, see Task Parallelism. Invoke Метод также имеются перегрузки, принимающие Action или Func<TResult> как параметр.The Invoke method also has overloads that take an Action or Func<TResult> as a parameter. Можно использовать Invoke вызывает синхронный метод, передав делегат, Action или Func<TResult>.You can use the Invoke method to make synchronous calls by passing in a delegate, Action or Func<TResult>.

Общие сведения и DispatcherOverview and the Dispatcher

Как правило WPFWPF приложения начинается с двух потоков: одного для обработки визуализации, а другой — для управления ИПUI.Typically, WPFWPF applications start with two threads: one for handling rendering and another for managing the ИПUI. Поток визуализации эффективно выполняется незаметно для пользователя в фоновом режиме при ИПUI поток получает входные данные, обрабатывает события, выводит изображение на экран и выполняет код приложения.The rendering thread effectively runs hidden in the background while the ИПUI thread receives input, handles events, paints the screen, and runs application code. Большинство приложений используют один ИПUI поток, несмотря на то, что в некоторых ситуациях лучше использовать несколько.Most applications use a single ИПUI thread, although in some situations it is best to use several. Позже это будет рассмотрено на примере.We’ll discuss this with an example later.

ИПUI Очереди потоков рабочие элементы внутри объекта, называемого Dispatcher.The ИПUI thread queues work items inside an object called a Dispatcher. Объект Dispatcher выбирает рабочие элементы на основе приоритетов и выполняет каждый из них до завершения.The Dispatcher selects work items on a priority basis and runs each one to completion. Каждый ИПUI поток должен иметь по крайней мере Dispatcherи каждый Dispatcher может выполнять рабочие элементы только в одном потоке.Every ИПUI thread must have at least one Dispatcher, and each Dispatcher can execute work items in exactly one thread.

Условием для построения быстро реагирующих, понятных пользователю приложений является максимальное повышение Dispatcher пропускной способности путем сохранения небольших рабочие элементы.The trick to building responsive, user-friendly applications is to maximize the Dispatcher throughput by keeping the work items small. Таком методе элементы никогда не устаревают Dispatcher очереди, ожидающих обработки.This way items never get stale sitting in the Dispatcher queue waiting for processing. Любая задержка между входными данными и ответами может разочаровать пользователя.Any perceivable delay between input and response can frustrate a user.

Как в таком WPFWPF приложения должны обрабатывать большие операции?How then are WPFWPF applications supposed to handle big operations? Что если код включает большие вычисления или требуется запрос к базе данных на удаленном сервере?What if your code involves a large calculation or needs to query a database on some remote server? Обычно ответ заключается в большие операции обрабатываются в отдельном потоке, оставляя ИПUI поток для обслуживания элементов в Dispatcher очереди.Usually, the answer is to handle the big operation in a separate thread, leaving the ИПUI thread free to tend to items in the Dispatcher queue. После завершения большой операции она может передать результат обратно ИПUI поток для отображения.When the big operation is complete, it can report its result back to the ИПUI thread for display.

Исторически сложилось так, что WindowsWindows позволяет ИПUI элементов был доступен только создавшему их потоку.Historically, WindowsWindows allows ИПUI elements to be accessed only by the thread that created them. Это означает, что фоновый поток, отвечающий за некоторую длительную задачу, не может обновить текстовое поле при своем завершении.This means that a background thread in charge of some long-running task cannot update a text box when it is finished. WindowsWindows Это делается, чтобы обеспечить целостность ИПUI компонентов.does this to ensure the integrity of ИПUI components. Список может выглядеть странно, если его содержимое обновляется фоновым потоком в процессе отображения.A list box could look strange if its contents were updated by a background thread during painting.

WPFWPF имеет встроенный механизм взаимного исключения, который осуществляет эту координацию.has a built-in mutual exclusion mechanism that enforces this coordination. Большинство классов в WPFWPF являются производными от DispatcherObject.Most classes in WPFWPF derive from DispatcherObject. При конструировании DispatcherObject хранит ссылку на Dispatcher связанный с текущим выполняемым потоком.At construction, a DispatcherObject stores a reference to the Dispatcher linked to the currently running thread. По сути DispatcherObject связывается с потоком, который его создал.In effect, the DispatcherObject associates with the thread that creates it. Во время выполнения программы DispatcherObject может вызвать свой открытый VerifyAccess метод.During program execution, a DispatcherObject can call its public VerifyAccess method. VerifyAccess проверяет Dispatcher связанный с текущим потоком и сравнивает его Dispatcher ссылка сохраняется во время создания.VerifyAccess examines the Dispatcher associated with the current thread and compares it to the Dispatcher reference stored during construction. Если они не совпадают, VerifyAccess возникло исключение.If they don’t match, VerifyAccess throws an exception. VerifyAccess предназначен для вызова в начале каждого метода, принадлежащего к DispatcherObject.VerifyAccess is intended to be called at the beginning of every method belonging to a DispatcherObject.

Если только один поток может изменить ИПUI, как фоновые потоки взаимодействуют с пользователем?If only one thread can modify the ИПUI, how do background threads interact with the user? Фоновый поток может попросить ИПUI поток, выполняющий операцию от его имени.A background thread can ask the ИПUI thread to perform an operation on its behalf. Это достигается путем регистрации рабочего элемента с Dispatcher из ИПUI потока.It does this by registering a work item with the Dispatcher of the ИПUI thread. Dispatcher Класс предоставляет два метода для регистрации рабочих элементов: Invoke и BeginInvoke.The Dispatcher class provides two methods for registering work items: Invoke and BeginInvoke. Оба метода назначают делегат для выполнения.Both methods schedule a delegate for execution. Invoke является синхронным вызовом — то есть он не возвращает до ИПUI потока не закончит выполнение делегата.Invoke is a synchronous call – that is, it doesn’t return until the ИПUI thread actually finishes executing the delegate. BeginInvoke является асинхронным и немедленно возвращает.BeginInvoke is asynchronous and returns immediately.

Dispatcher Упорядочивает элементы в своей очереди по приоритету.The Dispatcher orders the elements in its queue by priority. Существуют десять уровней, которые могут быть указаны при добавлении элемента к Dispatcher очереди.There are ten levels that may be specified when adding an element to the Dispatcher queue. Эти приоритеты сохраняются в DispatcherPriority перечисления.These priorities are maintained in the DispatcherPriority enumeration. Подробные сведения о DispatcherPriority уровней можно найти в Windows SDKWindows SDK документации.Detailed information about DispatcherPriority levels can be found in the Windows SDKWindows SDK documentation.

Потоки в действии: ПримерыThreads in Action: The Samples

Пример однопоточного приложения с длительным выполнением вычисленийA Single-Threaded Application with a Long-Running Calculation

Большинство графические пользовательские интерфейсы (GUI)graphical user interfaces (GUIs) тратят большую часть своего времени, простаивая в ожидании событий, которые создаются в ответ на действия пользователя.Most графические пользовательские интерфейсы (GUI)graphical user interfaces (GUIs) spend a large portion of their time idle while waiting for events that are generated in response to user interactions. При внимательном программировании это время простоя можно использовать конструктивно, не влияя на скорость реагирования ИПUI.With careful programming this idle time can be used constructively, without affecting the responsiveness of the ИПUI. WPFWPF Потоковая модель не позволяет вводу прерывать операцию, которая происходит в ИПUI потока.The WPFWPF threading model doesn’t allow input to interrupt an operation happening in the ИПUI thread. Это означает, что необходимо убедиться, чтобы вернуться к Dispatcher периодически, чтобы обработать отложенные события ввода, прежде чем они станут устаревшими.This means you must be sure to return to the Dispatcher periodically to process pending input events before they get stale.

Рассмотрим следующий пример.Consider the following example:

Снимок экрана, показывающий threading простых чисел.

Это простое приложение ищет простые числа, начиная от трех и далее.This simple application counts upwards from three, searching for prime numbers. Когда пользователь щелкает запустить кнопки, поиск начинается.When the user clicks the Start button, the search begins. Когда программа находит простое число, она обновляет пользовательский интерфейс.When the program finds a prime, it updates the user interface with its discovery. В любой момент пользователь может остановить поиск.At any point, the user can stop the search.

При всей простоте операции поиск простых чисел может происходить бесконечно, что представляет некоторые трудности.Although simple enough, the prime number search could go on forever, which presents some difficulties. Если бы обработка всех операций поиска в обработчик события нажатия кнопки, никогда бы не получил ИПUI потоков возможность обработки других событий.If we handled the entire search inside of the click event handler of the button, we would never give the ИПUI thread a chance to handle other events. ИПUI Бы ответить на входные данные или обработать сообщения.The ИПUI would be unable to respond to input or process messages. Он бы никогда не обновил отображение и не ответил бы на нажатие кнопки.It would never repaint and never respond to button clicks.

Можно провести поиск простого числа в отдельном потоке, но тогда пришлось бы иметь дело с проблемами синхронизации.We could conduct the prime number search in a separate thread, but then we would need to deal with synchronization issues. С помощью однопотокового подхода можно непосредственно обновить подпись, в которой перечислено наибольшее простое число.With a single-threaded approach, we can directly update the label that lists the largest prime found.

Если разбить задачу вычисления на управляемые фрагменты, можно периодически возвращаться к Dispatcher и обработки событий.If we break up the task of calculation into manageable chunks, we can periodically return to the Dispatcher and process events. Мы можем дать WPFWPF возможность обновлять и обрабатывать ввод.We can give WPFWPF an opportunity to repaint and process input.

Лучшим способом разбиения времени обработки между вычислением и обработкой события является управление вычислением из Dispatcher.The best way to split processing time between calculation and event handling is to manage calculation from the Dispatcher. С помощью BeginInvoke метод, можно запланировать проверку простого числа в той же очереди, ИПUI события, являются производными от.By using the BeginInvoke method, we can schedule prime number checks in the same queue that ИПUI events are drawn from. В приведенном примере запланирована проверка только одного простого числа в каждый момент времени.In our example, we schedule only a single prime number check at a time. После завершения проверки простого числа немедленно планируется следующая проверка.After the prime number check is complete, we schedule the next check immediately. Эта проверка выполняется только после ожидающих ИПUI обработки событий.This check proceeds only after pending ИПUI events have been handled.

Снимок экрана, показывающий очереди диспетчера.

С помощью этого механизма приложение Microsoft WordMicrosoft Word выполняет проверку орфографии.Microsoft WordMicrosoft Word accomplishes spell checking using this mechanism. Проверка орфографии выполняется в фоновом режиме, используя время простоя ИПUI потока.Spell checking is done in the background using the idle time of the ИПUI thread. Давайте посмотрим на код.Let's take a look at the code.

В следующем примере показан код XAML, который создает пользовательский интерфейс.The following example shows the XAML that creates the user interface.

<Window x:Class="SDKSamples.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://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>

В следующем примере показан код программной части.The following example shows the code-behind.

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;
    }
}
Imports System
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Threading
Imports System.Threading

Namespace SDKSamples
    Partial Public Class MainWindow
        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 Math.Sqrt(num)
                If num Mod i = 0 Then
                    ' Set not a prime flag to true.
                    NotAPrime = True
                    Exit For
                End If
            Next

            ' 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

В следующем примере показан обработчик событий для Button.The following example shows the event handler for the Button.

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));
    }
}
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

Помимо обновления текста в Button, этот обработчик отвечает за планирование проверки первого простого числа путем добавления делегата к Dispatcher очереди.Besides updating the text on the Button, this handler is responsible for scheduling the first prime number check by adding a delegate to the Dispatcher queue. Иногда после завершения работы, этот обработчик событий Dispatcher выберет этот делегат для выполнения.Sometime after this event handler has completed its work, the Dispatcher will select this delegate for execution.

Как было упомянуто ранее, BeginInvoke является Dispatcher членом, который используется при планировании делегата для выполнения.As we mentioned earlier, BeginInvoke is the Dispatcher member used to schedule a delegate for execution. В этом случае мы выбираем SystemIdle приоритет.In this case, we choose the SystemIdle priority. Dispatcher Будет выполнять данный делегат только в том случае, если отсутствуют важные события для обработки.The Dispatcher will execute this delegate only when there are no important events to process. Быстродействие ИПUI представляет большую важность, чем проверка числа.ИПUI responsiveness is more important than number checking. Также передается новый делегат, представляющий подпрограмму проверки числа.We also pass a new delegate representing the number-checking routine.

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;
Public Sub CheckNextNumber()
    ' Reset flag.
    NotAPrime = False

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

    ' 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

Этот метод проверяет, является ли следующее нечетное число простым.This method checks if the next odd number is prime. Если оно простое, метод непосредственно обновляет bigPrime TextBlock в соответствии с его обнаружения.If it is prime, the method directly updates the bigPrimeTextBlock to reflect its discovery. Мы можем сделать так потому, что вычисление происходит в том же потоке, который был использован для создания компонента.We can do this because the calculation is occurring in the same thread that was used to create the component. Бы мы решили использовать отдельный поток для вычислений, нам пришлось бы использовать более сложный механизм синхронизации и выполнять обновления в ИПUI потока.Had we chosen to use a separate thread for the calculation, we would have to use a more complicated synchronization mechanism and execute the update in the ИПUI thread. Эта ситуация будет продемонстрирована далее.We’ll demonstrate this situation next.

Полный исходный код для этого примера, см. в разделе Пример однопоточного приложения с образцом выполняющейся длительное время вычисленияFor the complete source code for this sample, see the Single-Threaded Application with Long-Running Calculation Sample

Обработка блокирующей операции с фоновым потокомHandling a Blocking Operation with a Background Thread

Обработка блокировки операций в графическом приложении может оказаться трудной задачей.Handling blocking operations in a graphical application can be difficult. Мы не будем вызывать методы блокировки из обработчиков событий, так как приложение будет остановлено.We don’t want to call blocking methods from event handlers because the application will appear to freeze up. Можно использовать отдельный поток для обработки этих операций, но когда все готово, у нас есть для синхронизации с ИПUI потоков, поскольку нельзя непосредственно изменить Графический интерфейс (GUI)GUI из рабочего потока.We can use a separate thread to handle these operations, but when we’re done, we have to synchronize with the ИПUI thread because we can’t directly modify the Графический интерфейс (GUI)GUI from our worker thread. Мы можем использовать Invoke или BeginInvoke вставку делегатов в Dispatcher из ИПUI потока.We can use Invoke or BeginInvoke to insert delegates into the Dispatcher of the ИПUI thread. Наконец, эти делегаты будут выполнены с разрешением на изменение ИПUI элементов.Eventually, these delegates will be executed with permission to modify ИПUI elements.

В этом примере мы имитируем вызов удаленной процедуры, который получает прогноз погоды.In this example, we mimic a remote procedure call that retrieves a weather forecast. Мы используем отдельный рабочий поток для выполнения этого вызова и планируем метод обновления в Dispatcher из ИПUI потоков, когда мы закончили.We use a separate worker thread to execute this call, and we schedule an update method in the Dispatcher of the ИПUI thread when we’re finished.

Снимок экрана, показывающий погоды пользовательского интерфейса.

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);
        }        
    }
}

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

Ниже приведены некоторые подробности, на которые следует обратить внимание.The following are some of the details to be noted.

  • Создание обработчика кнопкиCreating the Button Handler

    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 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
    

При нажатии кнопки мы отображаем рисунок часов и запускаем анимацию.When the button is clicked, we display the clock drawing and start animating it. Мы отключаем кнопку.We disable the button. Мы вызываем FetchWeatherFromServer метод в новом потоке, а затем мы возвращаем, позволяя Dispatcher для обработки событий во время ожидания сбора прогноза погоды.We invoke the FetchWeatherFromServer method in a new thread, and then we return, allowing the Dispatcher to process events while we wait to collect the weather forecast.

  • Выборка погодыFetching the Weather

    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 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
    

Для простоты мы фактически не используем никакого сетевого кода в данном примере.To keep things simple, we don’t actually have any networking code in this example. Вместо этого мы моделируем задержку доступа к сети, задав для нашего нового потока спящий режим в течение четырех секунд.Instead, we simulate the delay of network access by putting our new thread to sleep for four seconds. В настоящее время исходного ИПUI поток по-прежнему выполняется и реагирование на события.In this time, the original ИПUI thread is still running and responding to events. Чтобы показать это, была оставлена запущенная анимация, и кнопки свертывания и развертывания также продолжают работать.To show this, we’ve left an animation running, and the minimize and maximize buttons also continue to work.

После завершения задержки и случайного выбора прогноза погоды, настала пора докладываю ИПUI потока.When the delay is finished, and we’ve randomly selected our weather forecast, it’s time to report back to the ИПUI thread. Это делается путем создания расписания для вызова UpdateUserInterface в ИПUI потока с помощью этого потока Dispatcher.We do this by scheduling a call to UpdateUserInterface in the ИПUI thread using that thread’s Dispatcher. В запланированный вызов этого метода передается строка, описывающая погоду.We pass a string describing the weather to this scheduled method call.

  • Обновление ИПUIUpdating the ИПUI

    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 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
    

Когда Dispatcher в ИПUI времени у потока, он выполняет запланированный вызов метода UpdateUserInterface.When the Dispatcher in the ИПUI thread has time, it executes the scheduled call to UpdateUserInterface. Этот метод останавливает анимацию часов и выбирает изображение для описания погоды.This method stops the clock animation and chooses an image to describe the weather. Он отображает это изображение и восстанавливает кнопку "Получить прогноз погоды".It displays this image and restores the "fetch forecast" button.

Несколько окон, несколько потоковMultiple Windows, Multiple Threads

Некоторые WPFWPF приложениям требуется несколько окон верхнего уровня.Some WPFWPF applications require multiple top-level windows. Тогда вполне приемлемым для одного потока /Dispatcher наилучшим образом сочетания для управления окнами, но иногда несколько потоков.It is perfectly acceptable for one Thread/Dispatcher combination to manage multiple windows, but sometimes several threads do a better job. Это особенно верно, когда существует возможность, что одно из окон будет монополизировать поток.This is especially true if there is any chance that one of the windows will monopolize the thread.

Проводник WindowsWindows работает таким образом.WindowsWindows Explorer works in this fashion. Каждое новое окно проводника принадлежит исходному процессу, однако оно создается под управлением независимого потока.Each new Explorer window belongs to the original process, but it is created under the control of an independent thread.

С помощью WPFWPF Frame элемента управления, мы можем отобразить веб-страниц.By using a WPFWPFFrame control, we can display Web pages. Можно легко создать простой Internet ExplorerInternet Explorer заменить.We can easily create a simple Internet ExplorerInternet Explorer substitute. Начнем с важной функции: возможности открыть новое окно браузера.We start with an important feature: the ability to open a new explorer window. Когда пользователь нажимает кнопку "Новое окно", запускается копия окна в отдельном потоке.When the user clicks the "new window" button, we launch a copy of our window in a separate thread. Таким образом, долго выполняющиеся или блокирующие операции в одном из окон не блокируют все остальные окна.This way, long-running or blocking operations in one of the windows won’t lock all the other windows.

На самом деле у браузера имеется своя собственная сложная поточная модель.In reality, the Web browser model has its own complicated threading model. Мы выбрали его, поскольку он знаком большинству читателей.We’ve chosen it because it should be familiar to most readers.

В следующем примере показан код.The following example shows the code.

<Window x:Class="SDKSamples.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://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>
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("http://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();
        }
    }
}

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("http://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

В данном контексте наиболее интересными являются следующие сегменты потоков этого кода:The following threading segments of this code are the most interesting to us in this context:

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 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

Этот метод вызывается при нажатии кнопки "Новое окно".This method is called when the "new window" button is clicked. Она создает новый поток и запускает его в асинхронном режиме.It creates a new thread and starts it asynchronously.

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

Этот метод является начальной точкой для нового потока.This method is the starting point for the new thread. Мы создаем новое окно под элементом управления этого потока.We create a new window under the control of this thread. WPFWPF автоматически создает новую Dispatcher для управления новым потоком.automatically creates a new Dispatcher to manage the new thread. Все что нужно сделать для обеспечения функциональности окна — начать Dispatcher.All we have to do to make the window functional is to start the Dispatcher.

Технические подробности и важные моментыTechnical Details and Stumbling Points

Написание компонентов, использующих потокWriting Components Using Threading

Руководство разработчика Microsoft .NET Framework описывается шаблон того, как компонент может предоставлять асинхронное поведение для своих клиентов (см. в разделе Обзор асинхронной модели на основе событий).The Microsoft .NET Framework Developer's Guide describes a pattern for how a component can expose asynchronous behavior to its clients (see Event-based Asynchronous Pattern Overview). Например, предположим, что нужно упаковать FetchWeatherFromServer метод в неграфический компонент многократного использования.For instance, suppose we wanted to package the FetchWeatherFromServer method into a reusable, nongraphical component. Следующий стандартный шаблон Microsoft .NET Framework это будет выглядеть примерно следующим образом.Following the standard Microsoft .NET Framework pattern, this would look something like the following.

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);
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)

GetWeatherAsync использовал бы один из методов, описанных выше, таких как создание фонового потока, для работы в асинхронном режиме, не блокируя вызов потока.GetWeatherAsync would use one of the techniques described earlier, such as creating a background thread, to do the work asynchronously, not blocking the calling thread.

Одна из наиболее важных частей этого шаблона является вызов имя_метода Completed метод в том же потоке, который вызвал имя_метода Async метод начинается с.One of the most important parts of this pattern is calling the MethodNameCompleted method on the same thread that called the MethodNameAsync method to begin with. Это можно сделать с помощью WPFWPF довольно просто, сохранив CurrentDispatcher, но затем неграфический компонент может использоваться только в WPFWPF приложений, не в Windows FormsWindows Forms или ASP.NETASP.NET программы.You could do this using WPFWPF fairly easily, by storing CurrentDispatcher—but then the nongraphical component could only be used in WPFWPF applications, not in Windows FormsWindows Forms or ASP.NETASP.NET programs.

DispatcherSynchronizationContext Класс адреса этой задачи — представляйте его упрощенную версию Dispatcher , работает с другими ИПUI также платформ.The DispatcherSynchronizationContext class addresses this need—think of it as a simplified version of Dispatcher that works with other ИПUI frameworks as well.

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();
}
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

Вложенная накачкаNested Pumping

Иногда нецелесообразно полностью заблокировать ИПUI потока.Sometimes it is not feasible to completely lock up the ИПUI thread. Давайте рассмотрим Show метод MessageBox класса.Let’s consider the Show method of the MessageBox class. Show не возвращает, пока пользователь не щелкнет "ОК".Show doesn’t return until the user clicks the OK button. Однако он создает окно, которое должно иметь цикл обработки сообщений, чтобы быть интерактивным.It does, however, create a window that must have a message loop in order to be interactive. Ожидая, когда пользователь нажмет кнопку "ОК", исходное окно приложения не отвечает на ввод данных пользователем.While we are waiting for the user to click OK, the original application window does not respond to user input. Тем не менее оно продолжает обрабатывать сообщения отображения.It does, however, continue to process paint messages. Исходное окно перерисовывается при его перекрытии и выведении.The original window redraws itself when covered and revealed.

Снимок экрана, показывающий MessageBox с кнопку "ОК"

Данное окно сообщения должно подчиняться какому-либо потоку.Some thread must be in charge of the message box window. Приложение WPFWPF могло бы создать новый поток специально для данного окна сообщения, но этот поток не смог бы отображать отключенные элементы в исходном окне (вспомните предыдущее обсуждение взаимного исключения).WPFWPF could create a new thread just for the message box window, but this thread would be unable to paint the disabled elements in the original window (remember the earlier discussion of mutual exclusion). Вместо этого WPFWPF использует систему обработки вложенных сообщений.Instead, WPFWPF uses a nested message processing system. Dispatcher Класс включает специальный метод PushFrame, который хранит текущей точки выполнения приложения, затем начинает новый цикл обработки сообщений.The Dispatcher class includes a special method called PushFrame, which stores an application’s current execution point then begins a new message loop. После завершения цикла обработки вложенных сообщений выполнение возобновляется после исходного PushFrame вызова.When the nested message loop finishes, execution resumes after the original PushFrame call.

В этом случае PushFrame поддерживает программный контекст при вызове MessageBox.Show, и он начинает новый цикл обработки сообщений для перерисовки фона окна и обработки входных данных для окна сообщения.In this case, PushFrame maintains the program context at the call to MessageBox.Show, and it starts a new message loop to repaint the background window and handle input to the message box window. Когда пользователь нажимает кнопку ОК и очищает всплывающее окно, вложенные циклы завершаются и управление возобновляется после вызова Show.When the user clicks OK and clears the pop-up window, the nested loop exits and control resumes after the call to Show.

Устаревшие перенаправленные событияStale Routed Events

Маршрутизация системы обработки событий в WPFWPF уведомляет все деревья, когда вызываются события.The routed event system in WPFWPF notifies entire trees when events are raised.

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

При нажатии левой кнопки мыши над эллипсом, handler2 выполняется.When the left mouse button is pressed over the ellipse, handler2 is executed. После handler2 окончания события передается вдоль Canvas объект, который использует handler1 для его обработки.After handler2 finishes, the event is passed along to the Canvas object, which uses handler1 to process it. Это происходит только в том случае, если handler2 не задает явно пометить объект события как обработанные.This happens only if handler2 does not explicitly mark the event object as handled.

Возможно, handler2 займет немало времени, обработка этого события.It’s possible that handler2 will take a great deal of time processing this event. handler2 может использовать PushFrame для начала цикла вложенных сообщений, который не возвращает часов.handler2 might use PushFrame to begin a nested message loop that doesn’t return for hours. Если handler2 не помечает событие как обработанное после цикла обработки сообщений завершить, событие передается вверх по дереву, несмотря на то, что оно является очень старым.If handler2 does not mark the event as handled when this message loop is complete, the event is passed up the tree even though it is very old.

Повторный вход и блокировкаReentrancy and Locking

Механизм блокировки среда CLRcommon language runtime (CLR) не ведут себя точно так, как это можно представить; можно ожидать, что поток полностью завершает операцию, запрашивая блокировку.The locking mechanism of the среда CLRcommon language runtime (CLR) doesn’t behave exactly as one might imagine; one might expect a thread to cease operation completely when requesting a lock. В действительности поток продолжает получать и обрабатывать сообщения с высоким приоритетом.In actuality, the thread continues to receive and process high-priority messages. Это помогает избежать взаимоблокировок и максимально повышает скорость отклика интерфейсов, но может приводить к незначительным ошибкам.This helps prevent deadlocks and make interfaces minimally responsive, but it introduces the possibility for subtle bugs. Подавляющее большинство времени, не нужно ничего знать об этом, но в редких случаях (как правило с участием Win32Win32 сообщения окна или компоненты COM STA) это может быть знания.The vast majority of the time you don’t need to know anything about this, but under rare circumstances (usually involving Win32Win32 window messages or COM STA components) this can be worth knowing.

Большинство интерфейсов построено без учета безопасности потоков не так, как разработчики работают в предположении, что ИПUI никогда не осуществляется более чем одним потоком.Most interfaces are not built with thread safety in mind because developers work under the assumption that a ИПUI is never accessed by more than one thread. В данном случае, что вносимые одним потоком при изменении среды в непредвиденное время неблагоприятные последствия, DispatcherObject предполагается механизм взаимного исключения.In this case, that single thread may make environmental changes at unexpected times, causing those ill effects that the DispatcherObject mutual exclusion mechanism is supposed to solve. Рассмотрим следующий псевдокод:Consider the following pseudocode:

Схема, показывает, работа с потоками повторный вход. Diagram that shows threading reentrancy.

Большую часть времени все работает правильно, но бывают случаи, в WPFWPF где непредвиденный повторный вход может действительно вызвать проблемы.Most of the time that’s the right thing, but there are times in WPFWPF where such unexpected reentrancy can really cause problems. В этом случае в некий ключевой момент WPFWPF вызовы DisableProcessing, который меняет инструкцию блокировки для этого потока использовать WPFWPF свободную от повторного входа блокировку, вместо обычной CLRCLR блокировки.So, at certain key times, WPFWPF calls DisableProcessing, which changes the lock instruction for that thread to use the WPFWPF reentrancy-free lock, instead of the usual CLRCLR lock.

Так почему было CLRCLR team выбрала такое поведение?So why did the CLRCLR team choose this behavior? Это было связано с объектами COM STA и завершением потока.It had to do with COM STA objects and the finalization thread. Когда объект удаляется сборщиком мусора, его Finalize метод выполняется в выделенном потоке метода завершения, не ИПUI потока.When an object is garbage collected, its Finalize method is run on the dedicated finalizer thread, not the ИПUI thread. Этой последовательности заключена проблема, так как объект COM STA, который был создан на ИПUI поток может быть удален только в ИПUI потока.And therein lies the problem, because a COM STA object that was created on the ИПUI thread can only be disposed on the ИПUI thread. CLRCLR Предоставляет эквивалент BeginInvoke (в данном случае с помощью Win32 SendMessage).The CLRCLR does the equivalent of a BeginInvoke (in this case using Win32’s SendMessage). Но если ИПUI поток занят, поток метода завершения устаревает, и объект COM STA не удается завершить, что приводит к серьезной утечке памяти.But if the ИПUI thread is busy, the finalizer thread is stalled and the COM STA object can’t be disposed, which creates a serious memory leak. Поэтому CLRCLR создала сложный вызов для создания блокировки работать так, они делают.So the CLRCLR team made the tough call to make locks work the way they do.

Задача для WPFWPF — избежать непредвиденного повторного входа без внесения утечки памяти, поэтому мы не блокируем где.The task for WPFWPF is to avoid unexpected reentrancy without reintroducing the memory leak, which is why we don’t block reentrancy everywhere.

См. такжеSee also