Чтобы создать процедуры для вызова элементов управления, выполните следующие действия
1. Откройте редактор кода для формы frmCalculations. В разделе объявлений добавьте следующий код.
public delegate void FHandler(double Value, double Calculations);
public delegate void A2Handler(int Value, double Calculations);
public delegate void LDHandler(double Calculations, int Count);
В методах Invoke и BeginInvoke в качестве аргумента должен быть указан делегат для вызываемого метода. Эти строки объявляют сигнатуры делегатов, которые будут использоваться методом BeginInvoke для вызова соответствующих методов.
2. Добавьте в код следующие пустые методы.
public void FactHandler(double Value, double Calculations)
{
}
public void Fact1Handler(double Value, double Calculations)
{
}
public void Add2Handler(int Value, double Calculations)
{
}
public void LDoneHandler(double Calculations, int Count)
{
}
3. В меню Правка с помощью команд Вырезать и Вставить вырежьте весь код метода FactorialHandler и вставьте его в метод FactHandler.
4. Повторите предыдущий шаг для методов FactorialMinusHandler, Fact1Handler, AddTwoHandler, Add2Handler, LoopDoneHandler и LDoneHandler.
В результате в методах FactorialHandler, Factorial1Handler, AddTwoHandler и LoopDoneHandler код остаться не должен. Он должен быть перемещен в соответствующие новые методы.
5. Для асинхронного вызова методов вызовите метод BeginInvoke. Метод BeginInvoke можно вызвать либо из самой формы (this), либо из любого элемента управления в форме.
В результате код должен выглядеть примерно следующим образом.
protected void FactorialHandler(double Value, double Calculations)
{
// BeginInvoke causes asynchronous execution to begin at the address
// specified by the delegate. Simply put, it transfers execution of
// this method back to the main thread. Any parameters required by
// the method contained at the delegate are wrapped in an object and
// passed.
this.BeginInvoke(new FHandler(FactHandler), new Object[]
{Value, Calculations});
}
protected void FactorialMinusHandler(double Value, double Calculations)
{
this.BeginInvoke(new FHandler(Fact1Handler), new Object []
{Value, Calculations});
}
protected void AddTwoHandler(int Value, double Calculations)
{
this.BeginInvoke(new A2Handler(Add2Handler), new Object[]
{Value, Calculations});
}
protected void LoopDoneHandler(double Calculations, int Count)
{
this.BeginInvoke(new LDHandler(LDoneHandler), new Object[]
{Calculations, Count});
}
Может показаться, что обработчик событий просто вызывает очередной метод. На самом деле, обработчик событий инициирует метод в главном потоке операций. Этот же подход сохраняется и для вызовов через границы потоков, и позволяет многопоточным приложениям работать эффективно, не вызывая блокировку.
6. Сохраните результаты работы.
7. Проверьте решение, выбрав команду Начать отладку в меню Отладка.
a. Введите в текстовое поле значение 10000000 и нажмите кнопку Выполнить цикл.
В метке под кнопкой будет отображен текст "Выполнение цикла". Выполнение этого цикла занимает значительное количество времени. Если он завершается слишком рано, увеличьте число соответствующим образом.
b. Быстро нажмите подряд все три кнопки, которые пока доступны. Все кнопки отреагируют на ввод данных. Первым должен появиться результат в метке под кнопкой Прибавить два. Следующими появятся результаты в метках под кнопками факториалов. Результатом в этих случаях будет бесконечность, поскольку число, возвращаемое при вычислении факториала для 10 000 000, слишком велико для хранения в переменной с двойной точностью. Затем после некоторой задержки появятся результаты под кнопкой с надписью Выполнить цикл.
Таким образом, четыре отдельных группы вычислений были выполнены одновременно в четырех отдельных потоках. Интерфейс пользователя мог реагировать на ввод данных, и результаты возвращались после завершения работы каждого потока.
Координирование потоков
Опытный пользователь многопоточных приложений может заметить во введенном коде небольшие ошибки. Рассмотрим вновь следующие строки кода, имеющиеся в каждом методе вычислений в файле Calculator:
varTotalCalculations += 1;
varTotalAsOfNow = varTotalCalculations;
Эти две строки кода увеличивают общую переменную varTotalCalculations и присваивают ее значение локальной переменной varTotalAsOfNow. Это значение затем возвращается в форму frmCalculations и отображается в метке. Однако неизвестно, будет ли возвращено правильное значение. Если работает только один поток выполнения, то будет возвращено правильное значение. Однако если работают несколько потоков, правильность значения гарантировать нельзя. Каждый поток может увеличивать переменную varTotalCalculations. После того как один поток увеличит значение этой переменной, но перед тем как оно скопируется в varTotalAsOfNow, другой поток может также увеличить значение этой переменной. В итоге становится возможным, что каждый из потоков сообщит неточные результаты. В состав Visual C# входит Оператор lock, обеспечивающий синхронизацию потоков. Это гарантирует точность результатов, возвращаемых каждым потоком. Синтаксис функции lock выглядит следующим образом:
lock(AnObject)
{
// Insert code that affects the object.
// Insert more code that affects the object.
// Insert more code that affects the object.
// Release the lock.
}
Если введен блок lock, выполнение указанного выражения блокируется до тех пор, пока данный поток не снимет монопольную блокировку с рассматриваемого объекта. В приведенном выше примере выполнение блокируется для объекта AnObject. Оператор lock следует применять к объекту, который возвращает ссылку, а не значение. Выполнение может затем продолжиться в виде блока, защищенного от воздействия со стороны других потоков. Набор операторов, которые выполняются как единый блок, называется атомарным. При появлении знака } выражение освобождается и потоки могут продолжать работу.