Главная » WPF » Приложение. Базовые  службы

0

Синкатегориматический – не могущий быть использованным  в качестве термина сам по себе; так говорят о таких членах предложения, как наре чие или предлог.

Полный исправленный словарь Вэбстера, 1996, 1998 MICRA, Inc.

На платформе Windows Presentation Foundation имеется весьма развитый  на бор служб. В настоящей  книге я ставил себе целью изложить  основные  концеп ции платформы, что и было сделано в предшествующих восьми главах. Но служ бы зачастую оказываются очень полезны, и их рассмотрение  поможет понять, как построена система в целом и как ее части сопрягаются друг с другом. В этом при ложении  я расскажу  о некоторых  из своих любимых служб и продемонстрирую, как ими можно воспользоваться в приложении.

Потоки и диспетчеры

В самом начале разработки  WPF мы задались  целью устранить  зависимость от модели с однопотоковым контейнером (STA). Для многих элементов управле ния ActiveX и других служб на основе COM  необходима  именно потоковая мо дель STA, требующая,  чтобы в каждый  момент времени  код компонента  испол нялся  только в одном потоке, причем всегда одном и том же. В настоящее время почти все пользовательские интерфейсы в Windows работают в предположении модели STA.

Проблема  STA заключается в том, что все объекты привязаны к единственно му потоку и не могут переместиться ни в какой другой. Требование  о том, чтобы некий фрагмент кода исполнялся одним потоком, – это очень эффективное упро щение, но раз и навсегда привязывать исполнение  к единственному потоку, – по жалуй, чересчур. Увы, когда версия «Longhorn» превратилась в Vista, мы осозна ли, что для того, чтобы WPF могла работать с существующими службами (буфер обмена, Internet Explorer  и т.д.), нам придется  поддержать  STA. Поэтому  мы ре шили создать одну потоковую модель, а не поддерживать несколько.

Поэтому, к моему величайшему сожалению, я вынужден  сообщить, что WPF требует  потоковой  модели STA. Впервые  мы сталкиваемся с этим требованием при написании простейшего  приложения «Здравствуй, мир»; его точка входа по мечена атрибутом STAThread:

using System;

using System.Windows;

class Program {

[STAThread]

static void Main() {

Application app = new Application(); Window w = new Window();

w.Show();

app.Run();

}

}

Большинство объектов WPF наследуют общему базовому типу DispatcherObject. Отчасти вследствие первоначального намерения избавиться от STA мы привязываем объекты не к потоку, а к одному диспетчеру. Диспетчер  – это объект, который  получает  сообщения  (в CLR они называются событиями,  в библиотеке  User32  – сообщениями) и направляет их нужному  объекту.  Типы, имеющие отношение  к диспетчеру  и к потоковой  модели WPF,  по большей час ти находятся  в пространстве  имен System.Windows.Threading.

Истинный параллелизм с разделением памяти  (многопоточность) почти не возможно  запрограммировать в основных современных  языках. Проблема  в том, что приходится  иметь дело со сложными правилами управления блокировками и параллелизмом. В случае некорректной работы с блокировками мы очень быстро столкнемся  с «гонкой», клинчами, активными взаимоблокировками и порчей па мяти. Самая  успешная  модель одновременного выполнения нескольких  задач – это организация слабой связи между двумя задачами и применение асинхронных сообщений для передачи данных между двумя потоками.

Среди изменений в поддержке многопоточности, появившихся в .NET Framework 2.0, стоит упомянуть  класс SynchronizationContext. Он позволяет  со общить системе, как управлять одновременным выполнением. Конкретно,  в слу чае WPF в контексте сохраняется ссылка на диспетчера, в результате  чего любой асинхронный обратный вызов от различных компонентов  (например, System.Net.WebClient) становится возможно  переадресовать потоку пользова тельского интерфейса. Это происходит автоматически, так что мы можем вообще не заботиться о проблемах, связанных с многопоточностью.

Чтобы посмотреть, как это происходит  на практике,  напишем программу, ко торая  загружает  HTML  страницу с сайта. Сделать  это проще всего, воспользо вавшись методом DownloadStringAsync из класса WebClient:

<Window x:Class=’ThreadingExample.Window1’ xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’ xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml’ Title=’ThreadingExample’

<StackPanel>

<TextBox x:Name=’_text’ Height=’150’ HorizontalScrollBarVisibility=’Auto’ VerticalScrollBarVisibility=’Auto’ />

<Button Click=’DownloadNormally’>Download</Button>

</StackPanel>

</Window>

using System;

namespace ThreadingExample {

public partial class Window1 : Window {

public Window1() { InitializeComponent();

}

void DownloadNormally(object sender, RoutedEventArgs e) { WebClient wc = new WebClient(); wc.DownloadStringCompleted +=

delegate(object sender2,

DownloadStringCompletedEventArgs e2)

{

_text.Text = e2.Result;

};

wc.DownloadStringAsync(new Uri(«http://www.msn.com»));

}

}

}

Рис. A.1. Асинхронная загрузка HTML%страницы

Эта программа  выводит  результат,  показанный на рис. A.1. Объект  типа WebClient получает объект SynchronizationContext и затем использует его для воз буждения событий (это действие приводит к тому, что в потоке пользовательского интерфейса вызываются делегаты).  Такая модель позволяет  программировать асинхронные  действия, не вдаваясь в тонкости многопоточной обработки.

Иногда бывает необходимо организовывать многопоточность явно, нап ример, чтобы создать фоновый  поток, в котором выполняется задача, занима ющая много времени. Для таких случаев предусмотрено два способа обмена данными с потоком пользовательского интерфейса. Если имеется повторно используемый компонент,  который  может работать  и вне WPF  приложения (например, в приложении Windows Forms или ASP.NET), то нужно самосто ятельно  работать с обратными  вызовами  с помощью SynchronizationContext, как это делает WebClient. Если же реализуется прикладная логика, специ фичная  только  для конкретной  программы,  то можно напрямую  обращаться к диспетчеру,  ассоциированному с потоком  пользовательского интерфейса. Это дает более полный  контроль  над способом обработки  сообщений,  но мы уже  не  направляем  обратные   вызовы  в  правильный  контекст   в  Windows Forms или ASP.NET.

Независимо от метода создания  компонента  составные  части остаются  те ми же самыми. Нам необходим объект, представляющий задачу, и объект, представляющий результирующее «сообщение». В примере ниже создается задача, которая  вычисляет сумму целых чисел от 0 до n 1. Результатом явля ется объект  класса,  производного  от EventArgs,  который  содержит  получив шееся значение:

class SumCompletedEventArgs : EventArgs {

int _result;

public int Result {

get { return _result;}

set { _result = value;}

}

}

Для объекта  задачи достаточно простейшей объектной модели, которая долж на быть  согласована  с паттерном  асинхронного  компонента.  Нам  понадобится метод <DoSomething>Async и событие <DoSomething>Completed:

class Sum {

public Sum() { … }

public event EventHandler<SumCompletedEventArgs> SumCompleted;

public void SumAsync(int value) { … }

}

В данном случае обратными  вызовами будет заниматься объект SynchronizationContext, поэтому в конструкторе мы запомним текущий кон текст:

class Sum {

SynchronizationContext _context;

public Sum() {

_context = SynchronizationContext.Current;

}

}

Чтобы эта задача выполнялась в фоновом потоке, требуется  еще написать две функции. Первая реализует то, что собственно выполняется в фоновом потоке; ее мы назовем BackgroundTask. Вторая – Completed – это обратный вызов, который будет исполняться в потоке пользовательского интерфейса. Обратите  внимание, что из фонового  потока мы не обращаемся  к состоянию объекта, поэтому всякие конфликты исключены.  Чтобы  избежать  тонких  ошибок, связанных с многопо точностью, доступ к данным осуществляется только из потока пользовательско го интерфейса. Все необходимые  данные нужно скопировать в аргументы, пере даваемые фоновому  потоку:

class Sum {

SynchronizationContext _context;

public Sum() {

_context = SynchronizationContext.Current;

}

public event EventHandler<SumCompletedEventArgs> SumCompleted;

public void SumAsync(int value) {

Thread background = new Thread(BackgroundTask); background.IsBackground = true; background.Start(value);

}

void BackgroundTask(object parameter) {

int value = (int)parameter;

int result = 0;

for (int i = 0; i <= value; i++) {

result += i; Thread.Sleep(10);

}

_context.Post(Completed, result);

}

void Completed(object result) {

if (SumCompleted != null) {

SumCompletedEventArgs e = new SumCompletedEventArgs();

e.Result = (int)result; SumCompleted(this, e);

}

}

}

Этот класс работает точно так же, как WebClient. Мы создаем объект, подпи

сываемся на событие и вызываем  метод SumAsync:

void RunTask(object sender, RoutedEventArgs e) {

Sum t = new Sum();

t.SumCompleted +=

delegate(object sender2, SumCompletedEventArgs e2) {

text.Text = e2.Result.ToString();

};

t.SumAsync(250);

}

Главное достоинство  использования диспетчера  WPF вместо объекта SynchronizationContext – возможность задать приоритет  обратного вызова пото ка пользовательского интерфейса. В перечислении System.Windows.Threading. DispatcherPriority определено 12 таких приоритетов.  Впрочем, проще воспользо ваться имеющимся в .NET компонентом BackgroundWorker, который сам работа ет с SynchronizationContext.

Источник: К. Андерсон  Основы  Windows Presentation Foundation. Пер. с англ. А. Слинкина — М.: ДМК Пресс, 2008 — 432 с.: ил.

По теме:

  • Комментарии