Опубликован: 23.04.2013 | Доступ: свободный | Студентов: 854 / 184 | Длительность: 12:54:00
Лекция 9:

Интерфейс и многопоточность

< Лекция 8 || Лекция 9: 12345 || Лекция 10 >
Аннотация: Правила организации интерфейса.

Организация интерфейса приложения требует выполнения нескольких правил:

  1. Интерфейс должен быть отделен от бизнес - логики приложения. Это означает, что логика приложения должна быть реализована в отдельных классах, отличных от интерфейсных классов, - классов, создаваемых системой при выборе типа проекта Console, Windows Form или WPF.
  2. Интерфейс приложения должен быть сменным. Полезно, когда приложение имеет разные формы интерфейса. Для исследовательских целей часто используется консольный интерфейс, для работы пользователя необходим визуальный интерфейс, поддерживаемый Windows Form проектами. Более изысканный интерфейс поддерживается WPF проектами. Проще всего смена интерфейса достигается за счет того, что классы, реализующие бизнес логику, собираются в динамическую библиотеку - DLL, которая легко подсоединяется к любому интерфейсному классу.
  3. Интерфейс приложения должен позволять наблюдать и управлять процессами, происходящими при выполнении логики приложения.

Наблюдение и управление процессами

Реализация первых двух правил понятна и хорошо описана, например в [9]. Сейчас нас будет интересовать реализация третьего правила, - как построить интерфейс, позволяющий наблюдать и управлять ходом выполнения процесса, реализующего бизнес-логику приложения.

При традиционном построении интерфейса следует выделять, собирая, например, в отдельный контейнер, входные параметры приложения, значения которых задает пользователь до запуска процесса на выполнение. В отдельный контейнер помещаются выходные данные процессы. И в простейшем случае визуальный интерфейс приложения прост: есть контейнеры "Вход", "Выход" и кнопка "Пуск", запускающая процесс, по окончании работы которого отображаются значения выходных параметров процесса. Когда все это реализуется в одном потоке, то, нажав кнопку "Пуск", нужно ждать окончания работы процесса. Если он, не дай бог зациклился, то единственное спасение - три заветные клавиши - Ctrl -Alt - Del.

В серьезных приложениях, требующих управления, необходимо выделять контейнеры для наблюдаемых и управляемых параметров. Наблюдаемые параметры - это те параметры, по значениям которых можно судить о том, как идет процесс, и следует ли применить к нему то или иное управляющее воздействие. Значения наблюдаемых параметров создаются процессом бизнес -логики и должны в момент их вычисления непосредственно отображаться в интерфейсе. Значения управляющих параметров задаются пользователем, управляющим процессом, и должны быть непосредственно восприняты управляемым процессом в момент их задания. Таким образом, процесс должен уметь "читать" значения управляющих параметров и "записывать" значения наблюдаемых параметров. Два процесса - управляющий и управляемый должны взаимодействовать и работать параллельно.

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

Методы Invoke, BeginInvoke, EndInvoke

При многопоточном программировании на C# элементы интерфейса не относятся к общим ресурсам, доступным для всех потоков. Поток не имеет права доступа к элементу интерфейса, созданному в другом потоке. Это означает, что дочерние потоки, реализующие бизнес-логику, не имеют права "чтения" и "записи" в элементы управления интерфейса, созданные в основном потоке. Организация взаимодействия между потоками, использующими общие элементы интерфейса, требует своего рода маршалинга, когда объект одного потока передается элементу управления другого потока, проходя нужные стадии преобразования. Такой маршалинг можно реализовать, используя метод Invoke.

Все элементы интерфейса обладают методом Invoke, наследуя его от класса Control. Метод перегружен и имеет две реализации:

Invoke(Delegate);
Invoke(Delegate, object[]);

В обеих реализациях первый аргумент функционального типа (delegate) задает метод, принадлежащий потоку, в котором определен элемент интерфейса. Именно этот метод и будет выполнять операцию над элементом интерфейса. Если нет необходимости передавать методу информацию, то используется реализация Invoke с одним аргументом. В этом случае в качестве функционального типа можно использовать стандартный тип - класс MethodInvoker из пространства System.Windows.Forms. Рассмотрим пример вызова метода Invoke:

myForm.Invoke(new MethodInvoker(myForm.SetVisions));

Здесь объект myForm, задающий форму, вызывает метод Invoke. В качестве фактического параметра ему передается метод SetVisions - метод без аргументов, соответствующий типу MethodInvoker. Метод SetVisions принадлежит тому же классу, что и объект myForm и потому может быть вызван этим объектом.

Чаще всего, методу, работающему с элементом управления, необходимо передать информацию, так что метод, вызываемый в Invoke, может иметь один или несколько аргументов. В этом случае необходимо определить соответствующий функциональный тип - аналог MethodInvoker, создать метод - экземпляр этого типа, передать этот метод в качестве первого аргумента методу Invoke, а необходимые данные передать в качестве второго аргумента. Второй аргумент метода Invoke задается массивом объектов, таким образом можно передать значения всех требуемых аргументов методу, работающему с элементом управления. Рассмотрим пример, когда элементу управления нужно передать строку текста - объект типа string. Объявим прежде всего соответствующий функциональный тип:

public delegate void Delegate_Void_String(string par);
Определим теперь экземпляр этого типа и свяжем с ним требуемый метод:
public Delegate_Void_String myDelegate;
myDelegate = AddList;

Метод AddList, добавляющий строку в элемент управления ListBox, определен следующим образом:

void AddList(string item)
        {
            listBoxValues.Items.Add(item);
        }

Теперь в другом потоке можем добавить данные в элемент управления, вызвав метод Invoke следующим образом:

correctForm.Invoke(correctForm.myDelegate, new object[] { res });

Здесь res задает передаваемую строку данных, добавляемую в список элемента управления.

Метод Invoke является синхронным методом, ожидающим завершения операции над элементом управления. Методы BeginInvoke и EndInvoke обеспечивают асинхронное выполнение. По принципу организации передачи информации они схожи, и я не буду останавливаться подробно на их рассмотрении.

< Лекция 8 || Лекция 9: 12345 || Лекция 10 >
Алексей Рыжков
Алексей Рыжков

не хватает одного параметра:

static void Main(string[] args)
        {
            x = new int[n];
            Print(Sample1,"original");
            Print(Sample1P, "paralel");
            Console.Read();
        }

Никита Белов
Никита Белов

Выставил оценки курса и заданий, начал писать замечания. После нажатия кнопки "Enter" окно отзыва пропало, открыть его снова не могу. Кнопка "Удалить комментарий" в разделе "Мнения" не работает. Как мне отредактировать недописанный отзыв?