412 000 произведений, 108 200 авторов.

Электронная библиотека книг » Иво Салмре » Программирование мобильных устройств на платформе .NET Compact Framework » Текст книги (страница 28)
Программирование мобильных устройств на платформе .NET Compact Framework
  • Текст добавлен: 18 июля 2025, 02:31

Текст книги "Программирование мобильных устройств на платформе .NET Compact Framework"


Автор книги: Иво Салмре



сообщить о нарушении

Текущая страница: 28 (всего у книги 69 страниц)

Потоки и пользовательский интерфейс

Время от времени приходится слышать вопрос: "Следует ли использовать несколько потоков для выполнения пользовательского интерфейса?" На этот вопрос почти однозначно должен быть дан отрицательный ответ. Привлекать для управления различными частями пользовательского интерфейса несколько потоков практически никогда не имеет смысла. Это особенно относится к мобильным устройствам, в которых пользовательские интерфейсы приложения, как правило, занимают весь экран.

Обычно окна связываются с потоком, который является их владельцем; это справедливо для операционных систем Windows СЕ, Pocket PC и Microsoft Smartphone (а также для таких настольных операционных систем, как Windows XP и более ранние версии Windows). Для каждого окна имеется поток, которому оно принадлежит и который им управляет. Один и тот же поток может владеть несколькими окнами. Поток играет роль "генератора сообщений" по отношению к этим окнам и пересылает им сообщения, когда окно должно быть перерисовано, когда нажимается клавиша, когда выполняется щелчок на кнопке и так далее.

Хотя и можно организовать приложение таким образом, чтобы пользовательский интерфейс обслуживался несколькими потоками (например, по одному потоку на одно окно верхнего уровня), это почти никогда не принесет никакой пользы. Это только усложняет структуру приложения, ничуть не ускоряя его работу. Если вы считаете, что для пользовательского интерфейса необходимо задействовать несколько потоков, задайте себе вопрос, а для чего это в действительности вам надо и нельзя ли при этом использовать один основной поток пользовательского интерфейса и несколько рабочих фоновых потоков, что сделает модель гораздо более понятной.

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

Даже если ваша оконная модель и не связана с какими-либо специфическими потоками, обычно целесообразнее иметь только один поток выполнения, который "берет на себя попечительство" над пользовательским интерфейсом.

Работая в .NET Compact Framework, не пытайтесь получить доступ к элементам управления пользовательского интерфейса из потоков, которым они не принадлежат

Ни вариант .NET Framework для настольных компьютеров, ни вариант .NET Compact Framework для мобильных устройств не поддерживают доступ к большинству свойств и методов элементов пользовательского интерфейса из потоков, которым они не принадлежат. Хотя такой код и будет нормально компилироваться, результаты его выполнения будут непредсказуемыми. Для обеспечения межпоточных вызовов в .NET Framework и .NET Compact Framework поддерживается метод Control.Invoke(). В версии 1.1 .NET Compact Framework поддерживается лишь использование механизма Control.Invoke() для вызова функций без параметров. Более подробную информацию относительно применения этого метода вы найдете в справочной документации MSDN. Не составляет труда организовать на приемлемом уровне обмен данными между фоновым потоком и потоком пользовательского интерфейса, предусмотрев для этого выполняющийся в потоке пользовательского интерфейса код, который периодически опрашивает объект, специально предназначенный для управления выполнением фоновых потоков, с целью определения того, имеются ли данные, ожидающие реакции пользовательского интерфейса. Обычно сделать это гораздо проще, чем погружаться во все тонкости межпоточного вызова методов.

Второй подход связан с использованием косвенно вызываемого делегата, указывающего на функцию формы вашего приложения. В качестве такой функции (не имеющей параметров – см. выше) может быть назначена функция класса формы, которую можно вызвать посредством метода Invoke() формы. Вызов Invoke() приведет к выполнению упомянутой функции в потоке пользовательского интерфейса. После этого функция может извлечь любые необходимые данные и соответствующим образом обновить пользовательский интерфейс. Достоинством такого подхода является то, что высокоприоритетный поток не должен выполнять цикл опроса и получает обновленную информацию сразу же после завершения фоновой работы. Недостаток этого подхода заключается в том, что он требует создания (предположительно, на очень короткое время) синхронной связи между фоновым и высокоприоритетным потоками выполнения. Когда делегат запускается на выполнение в фоновом потоке, выполнение фонового потока приостанавливается, контекст выполнения переключается на высокоприоритетный поток, и делегат начинает выполняться. Это препятствует переключению фонового потока на выполнение другой работы, находящейся в очереди. Выполнение фонового потока сможет возобновиться лишь после того, как выполнение делегата завершится.

Пример использования фоновой обработки одновременно с обновлением данных высокоприоритетного потока пользовательского интерфейса

Сейчас мы вернемся к нашему примеру с простыми числами, который рассматривался в главе 5, заметно изменив и усовершенствовав его. На этот раз мы создадим приложение типа SmartPhone, вычисляющее простые числа большой величины. Во время проведения большого объема вычислений фоновым потоком приложение будет сохранять способность к интерактивному взаимодействию с пользователем. Приложение предоставляет пользователю возможность при необходимости прекратить выполнение фонового потока. Кроме того, и это немаловажно, в приложении реализован неплохой способ информирования пользователя о состоянии выполнения интересующей его задачи. Зная, что фоновая задача успешно выполняется, пользователь будет чувствовать себя более комфортно

Данное приложение несложно адаптировать для выполнения на Pocket PC. Наш выбор Microsoft Smartphone в качестве целевой платформы был сделан исключительно в интересах разнообразия.

Рис. 9.2. Окно интегрированной среды разработки Visual Studio, предcтавляющее проектируемый пользовательский интерфейс приложения типа Smartphone


Рис. 9.3. Экранные снимки эмулятора Smartphone, полученные в процессе вычисления приложением простых чисел

НА ЗАМЕТКУ

Если вы используете Visual Studio .NET 2003, то вам необходимо загрузить SDK для Windows Mobile 2003-based Smartphones. Visual Studio NET 2003 поставлялась с "коробочным" вариантом средств разработки приложений для Pocket PC, но не для Smartphone. Поскольку SDK для Smartphone поставлялся после выхода Visual Studio .NET 2003, его следует загрузить и установить поверх Visual Studio .NET. Пакет SDK можно бесплатно загрузить с Web– сайта компании Microsoft (см. приложение А). Этот SDK включает в себя компоненты, необходимые для проектирования пользовательских интерфейсов Smartphone, а также эмулятор Smartphone, позволяющий выполнять приложения, даже если вы не располагаете физическим устройством Smartphone.

Чтобы создать и запустить указанное приложение, выполните следующие действия:

1. Запустите Visual Studio .NET (2003 или более позднюю версию) и создайте проект C# Smart Device Application.

2. Выберите в качестве целевой платформы Smartphone. (Для вас будет автоматически создан проект, и на экране появится конструктор форм для Smartphone.)

3. Используя рис. 9.2 в качестве образца для компоновки формы, добавьте в нее следующие элементы управления:

 • TextBox (textBox1); задайте в качестве значения свойства Text длинную текстовую строку (например, 12345678901234).

 • Label (label1); измените размеры элемента управления Label таким образом, чтобы он занимал большую часть области формы. В нем придется отображать текст, состоящий из нескольких строк.

 • Timer (timer1).

4. Выделите компонент MainMenu в нижней части окна конструктора форм и добавьте следующие пункты меню:

 • Перейдите к крайнему слева меню (содержащему текст "Type Here" ("Набирайте здесь")) и введите Exit в качестве текста меню. Используя окно Properties, измените имя элемента меню с menuItem1 на menuItemExit.

 • Справа от меню Exit, которое вы только что добавили (там, где находится текст "Type Here"), введите Prime Search в качестве текста меню. Примечание: при необходимости обратитесь к рис. 9.2.

 • Над меню Prime Search, которое вы только что добавили (там, где находится текст "Type Here"), введите Start в качестве текста меню. Используя окно Properties, измените имя элемента меню с menuItem2 на menuItemStart. Примечание: при необходимости обратитесь к рис. 9.2.

 • Ниже меню Start, которое вы только что добавили (там, где находится текст "Type Here"), введите Abort в качестве текста меню. Используя окно Properties, измените имя элемента меню с menuItem2 на menuItemStart. Примечание: при необходимости обратитесь к рис. 9.2.

5. Добавьте в проект новый класс. Назовите его FindNextPrimeNumber.сs. Замените содержимое кода класса в окне редактора кодом из листинга 9.5.

6. Перейдите обратно в окно Form1.cs [Design] и дважды щелкните на элементе меню Exit. В результате этого будет автоматически сгенерирована функция void menuItemExit_Click(), а фокус переместится в окно редактора кода. Введите для этой функции код из листинга 9.4.

7. Перейдите обратно в окно Form1.cs [Design] и дважды щелкните на элементе меню Start. В результате этого будет автоматически сгенерирована функция private void menuItemStart_Click(), а фокус переместится в окно редактора кода. Введите для этой функции код из листинга 9.4.

8. Перейдите обратно в окно Form1.cs [Design] и дважды щелкните на названии меню Abort. В результате этого будет автоматически сгенерирована функция private void menuItemAbort_Click(), а фокус переместится в окно редактора кода. Введите для этой функции код из листинга 9.4.

9. Перейдите обратно в окно Form1.CS [Design] и дважды щелкните на элементе управления timer1 в нижней части окна конструктора. В результате этого будет автоматически сгенерирована функция private void timer1_Tick(). Введите для этой функции код из листинга 9.4.

10. Введите в класс Form1.cs остальную часть кода из листинга 9.4.

11. Нажмите клавишу для компиляции кода и развертывания приложения в эмуляторе Smartphone.

Запустив приложение, нажмите сначала кнопку телефона для вызова меню Prime Search, а затем – клавишу <1> для выбора пункта меню Start В результате этого начнется поиск простых чисел. В процессе выполнения поиска элемент управления timer1 будет несколько раз в секунду вырабатывать событие таймера, заставляющее пользовательский интерфейс обновлять отображаемый на форме текст с информацией о текущем состоянии. В результате динамического обновления этого текста, происходящего несколько раз в секунду, пользователь получает регулярное подтверждение того, что его запрос обрабатывается. Процесс поиска продолжается до тех пор, пока не будет найдено простое число или пока пользователь не выберет пункт Abort меню Prime Search и не нажмет кнопку <2>. Чтобы увеличить продолжительность поиска, перед тем, как его начать, введите в текстовом поле формы произвольное большое число. На моем эмуляторе поиск для начального числа 12345678901234 длился более 20 секунд. В том виде, как он есть, приведенный ниже код не препятствует запуску нового поиска в то время, когда предыдущий еще не закончился. Будет неплохо, если вы усовершенствуете приложение, введя в него проверку этого условия и прекращая выполнение текущего поиска, если к моменту запуска нового он еще не был закончен. Рекомендуется также, чтобы вы заглянули в код, представленный в листинге 9.5. и посмотрели, как ключевое слово lock используется для того, чтобы исключить параллельное вхождение различных потоков в критический раздел, не являющийся безопасным в отношении многопоточного выполнения.

Листинг 9.4. Код, который должен быть помещен в класс Smartphone Form1.cs

//–

//Весь этот код должен находиться внутри класса Form1.cs

//–

//Объект, который будет выполнять все фоновые вычисления

FindNextPrimeNumber m_findNextPrimeNumber;

//–

//Обновить текст, информирующий о состоянии...

//–

void setCalculationStatusText(string text) {

 label1.Text = text;

}

//–

//Пункт меню для "выхода" из приложения

//–

private void menuItemExit_Click(object sender, System.EventArgs e) {

 this.Close();

}

//–

//Пункт меню для начала фоновых вычислений

//–

private void menuItemStart_Click(object sender, System.EventArgs e) {

 //Число, с которого мы хотим начать поиск

 long startNumber = System.Convert.ToInt64(textBox1.Text);

 //Установить фоновое выполнение

 m_findNextPrimeNumber = new FindNextPrimeNumber(startNumber);

 //Запустить выполнение задачи в фоновом режиме

 m_findNextPrimeNumber.findNextHighestPrime_Async();

 //Установить таймер, используемый для контроля длительности вычислений

 timer1.Interval = 400;

 //400 мс timer1.Enabled = true;

}

//–

//Пункт меню для "отмены" выполняющейся задачи

//–

private void menuItemAbort_Click(object sender, System.EventArgs e) {

 //He делать ничего, если вычисления не выполняются

 if (m_findNextPrimeNumber == null) return;

 //Установить поток в состояние прекращения выполнения

 m_findNextPrimeNumber.setProcessingState(FindNextPrimeNumber.ProcessingState.reguestAbort);

 //Немедленно известить пользователя

 //o готовности прекратить выполнение...

 setCalculationStatusText("Waiting to abort...");

 // setCalculationStatusText("Ожидание прекращения выполнения...");

}

//–

//Этот таймер, вызываемый потоком пользовательского интерфейса,

//позволяет отслеживать состояние выполнения

//фоновых вычислений

//–

private void timer1_Tick(object sender, System.EventArgs e) {

 //Если к моменту вызова искомое простое число еще

 //не было найдено, отключить таймер

 if (m_findNextPrimeNumber == null) {

  timer1.Enabled =false;

  return;

 }

 //–

 //Если выполнение было отменено, освободить объект,

 //осуществляющий поиск, и выключить таймер

 //–

 if (m_findNextPrimeNumber.getProcessingState ==

     FindNextPrimeNumber.ProcessingState.aborted) {

  timer1.Enabled = false;

  m_findNextPrimeNumber = null;

 setCalculationStatusText("Prime search aborted");

  // setCalculationStatusText("поиск простого числа отменен");

  return;

 }

 //–

 //Удалось ли найти правильный ответ?

 //–

 if (m_findNextPrimeNumber.getProcessingState ==

     FindNextPrimeNumber.ProcessingState.foundPrime) {

  timer1.Enabled = false;

  //Отобразить результат

  setCalculationStatusText("Found! Next Prime = " + m_findNextPrimeNumber.getPrime().ToString());

  // setCalculationStatusText("Чиcлo найдено! Следующее простое число = " +

  // m_findNextPrimeNumber.getPrime().ToString());

  m_findNextPrimeNumber = null;

  return;

 }

 //–

 //Вычисления продолжаются. Информировать

 //пользователя о состоянии выполнения

 //–

 //Получить два выходных значения

 long numberCalculationsToFar;

 long currentItem;

 m_findNextPrimeNumber.getExecutionProgressInfo(out numberCalculationsToFar,out currentItem);

 setCalculationStatusText("In progress. Looking at: " +

  currentItem.ToString() + ". " +

  numberCalculationsToFar.ToString() +

  " calculations done for you so far!");

 // setCalculationStatusText("Вычисления продолжаются. Поиск в области: " +

 // currentItem.ToString() + ". " +

 // "Для вас выполнено " +

 // numberCalculationsToFar.ToString() +

 // " расчетов!");

}

Листинг 9.5. Код класса FindNextPrimeNumber.cs

using System;

public class FindNextPrimeNumber {

 //Перечисляем возможные состояния

 public enum ProcessingState {

  notYetStarted,

  waitingToStartAsync,

  lookingForPrime,

  foundPrime,

  requestAbort,

  aborted

 }

 long m_startPoint;

 long m_NextHighestPrime;

 //Поиск какого количества элементов выполнен?

 long m_comparisonsSoFar;

 //Для какого элемента сейчас выполняется поиск простого числа?

 long m_CurrentNumberBeingExamined;

 //Вызывается для обновления информации о состоянии выполнения

 public void getExecutionProgressInfo(out long numberCalculationsSoFar, out long currentItemBeingLookedAt) {

  //ПРИМЕЧАНИЕ. Мы используем блокирование потока для уверенности в том,

  //что эти значения не считываются во время выполнения операции

  //их записи. Поскольку доступ к m_comparisonsSoFar

  //и m_CurrentNumberBeingExamined могут осуществлять

  //одновременно несколько потоков, любая выполняемая над ними

  //операция записи/считывания должна синхронизироваться с "блокировкой",

  //что будет гарантировать "атомарность" этих операций

  lock (this) {

   numberCalculationsSoFar = m_comparisonsSoFar;

   currentItemBeingLookedAt = m_CurrentNumberBeingExamined;

  }

 }

 ProcessingState m_processingState;

 //–

 //Простейший конечный автомат

 //–

 public void setProcessingState(ProcessingState nextState) {

  //–

  //Простейший защитный код, гарантирующий

  //невозможность перехода в другое состояние, если задача

  //либо успешно завершена, либо успешно отменена

  //–

  if ((m_processingState == ProcessingState.aborted) ||

      (m_processingState == ProcessingState.foundPrime)) {

   return;

  }

  //Разрешить изменение состояния

  m_processingState = nextState;

 }

 public ProcessingState getProcessingState {

  get {return m_processingState;}

 }

 //–

 //Возвращает простое число

 //–

 public long getPrime() {

  if (m_processingState != ProcessingState.foundPrime) {

   throw new Exception("простое число еще не найдено!");

  }

  return m_NextHighestPrime;

 }

 //Конструктор класса

 public FindNextPrimeNumber(long startPoint) {

  setProcessingState(ProcessingState.notYetStarted);

  m_startPoint = startPoint;

 }

 //–

 //Создает новый рабочий поток, который будет вызывать функцию

 // "findNextHighestPrime()"

 //–

 public void findNextHighestPrime_Async() {

  System.Threading.ThreadStart threadStart;

  threadStart = new System.Threading.ThreadStart(findNextHighestPrime);

  System.Threading.Thread newThread;

  newThread = new System.Threading.Thread(threadStart);

  //Состояние должно отвечать, что поиск продолжается

  setProcessingState(ProcessingState.waitingToStartAsync);

  newThread.Start();

 }

 //–

 //Основной рабочий поток. Этот поток запускает поиск очередного

 //простого числа и выполняется до тех пор, пока не произойдет

 //одно из следующих двух событий:

 // (а) найдено очередное простое число

 // (b) от внешнего (по отношению к данному) потока поступила

 // команда прекратить выполнение

 //–

 public void findNextHighestPrime() {

  //Если поступила команда прекратить выполнение, то поиск

  //даже не должен начинаться

  if (m_processingState == ProcessingState.requestAbort) {

   goto finished_looking;

  }

  //Состояние должно отвечать, что поиск продолжается

  setProcessingState(ProcessingState.lookingForPrime);

  long currentItem;

  //Проверить, является ли число нечетным

  if ((m_startPoint & 1) == 1) {

   //Число является нечетным, начать поиск со следующего нечетного числа

   currentItem = m_startPoint + 2;

  } else {

   //Число является четным, начать поиск со следующего нечетного числа

   currentItem = m_startPoint + 1;

  }

  //Приступить к поиску простого числа

  while (m_processingState == ProcessingState.lookingForPrime) {

   //B случае нахождения простого числа возвратить его

   if (isItemPrime(currentItem) == true) {

    m_NextHighestPrime = currentItem; //Обновить состояние

    setProcessingState(ProcessingState.foundPrime);

   }

   currentItem = currentItem + 2;

  }

finished_looking:

  //Выход. К этому моменту либо от другого потока поступила

  //команда прекратить поиск, либо было найдено и записано

  //следующее наибольшее простое число

  //Если поступил запрос прекратить выполнение,

  //сообщить, что выполнение процесса прекращено

  if (m_processingState == ProcessingState.requestAbort) {

   setProcessingState(ProcessingState.aborted);

  }

 }

 //Вспомогательная функция, которая проверяет, является

 //ли число простым

 private bool isItemPrime(long potentialPrime) {

  //Если число – четное, значит, оно не является простым

  if ((potentialPrime & 1) == 0) {

   return false;

  }

  //Продолжать поиск до тех пор, пока не будет превышено

  //значение квадратного корня из числа

  long end_point_of_search;

  end_point_of_search = (long)System.Math.Sqrt(potentialPrime) + 1;

  long current_test_.item = 3;

  while (current_test_item <= end_point_of search) {

   //–

   //Проверить, не поступила ли команда прекратить выполнение!

   //–

   if (m_processingState != ProcessingState.lookingForPrime) {

    return false;

   }

   //Если число делится без остатка,

   //значит, оно не является простым

   if (potentialPrime % current_test_item == 0) {

    return false;

   }

   //увеличить число на два

   current_test item = current_test_item + 2;

   //–

   //Увеличить количество проверенных элементов

   //–

   //ПРИМЕЧАНИЕ. Мы используем блокирование потока для уверенности в том,

   //что эти значения не считываются во время выполнения операции

   //их записи. Поскольку доступ к m_comparisonsSoFar

   //и m_CurrentNumberBeingExamined могут осуществлять

   //одновременно несколько потоков, любая выполняемая над ними

   //операция записи/считывания должна синхронизироваться с "блокировкой",

   //что будет гарантировать "атомарность" этих операций

   lock(this) {

    m_CurrentNumberBeingExamined = potentialPrime;

    m_comparisonsSoFar++;

   }

  }

  //Число является простым

  return true;

 } //Конец функции

} //Конец класса


    Ваша оценка произведения:

Популярные книги за неделю