prosdo.ru
добавить свой файл
1 ... 3 4 5 6

Чтобы добавить соответствующий код в форму "frmCalculations", выполните следующие действия.

  1. Откройте файл frmCalculations.cs в Редакторе кода и найдите метод private void btnFactorial1_Click.


    1. Преобразуйте строку, которая вызывает метод Calculator1.Factorial1, в комментарий следующим образом.

// Calculator1.Factorial()

    1. Добавьте следующую строку для вызова метода Calculator1.ChooseThreads.

// Passes the value 1 to Calculator1, thus directing it to start the

// correct thread.

Calculator1.ChooseThreads(1);

  1. Внесите аналогичные изменения в другие методы button_click.

примечаниеПримечание

Убедитесь в том, что для аргумента Threads задано соответствующее значение.

В результате код должен выглядеть примерно следующим образом.

private void btnFactorial1_Click(object sender, System.EventArgs e)

// Passes the value typed in the txtValue to Calculator.varFact1

{

Calculator1.varFact1 = int.Parse(txtValue.Text);

// Disables the btnFactorial1 until this calculation is complete

btnFactorial1.Enabled = false;

// Calculator1.Factorial();

Calculator1.ChooseThreads(1);

}
private void btnFactorial2_Click(object sender, System.EventArgs e)

{

Calculator1.varFact2 = int.Parse(txtValue.Text);

btnFactorial2.Enabled = false;

// Calculator1.FactorialMinusOne();

Calculator1.ChooseThreads(2);

}

private void btnAddTwo_Click(object sender, System.EventArgs e)

{

Calculator1.varAddTwo = int.Parse(txtValue.Text);


btnAddTwo.Enabled = false;

// Calculator1.AddTwo();

Calculator1.ChooseThreads(3);

}
private void btnRunLoops_Click(object sender, System.EventArgs e)

{

Calculator1.varLoopValue = int.Parse(txtValue.Text);

btnRunLoops.Enabled = false;

// Lets the user know that a loop is running

lblRunLoops.Text = "Looping";

// Calculator1.RunALoop();

Calculator1.ChooseThreads(4);

}

http://i.msdn.microsoft.com/hash/030c41d9079671d09a62d8e2c1db6973.gif

Маршалинг элементов управления

Теперь можно легко обновить отображение формы. Маршалинг — это процесс передачи вызова через границы потоков, который потребляет значительный объем ресурсов. Чтобы снизить до минимума объем необходимых операций маршалинга, а также устранить возможность возникновения конфликтов между потоками при обработке вызовов, следует использовать метод Control.BeginInvoke для вызова методов в главном потоке выполнения. Такой способ вызова необходим при обращении к методам, работающим с элементами управления.

Чтобы создать процедуры для вызова элементов управления, выполните следующие действия.


  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 для вызова соответствующих методов.


  1. Добавьте в код следующие пустые методы.

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)

{

}

  1. В меню Правка
    с помощью команд Вырезать и Вставить вырежьте весь код метода FactorialHandler и вставьте его в метод FactHandler.

  2. Повторите предыдущий шаг для методов FactorialMinusHandler, Fact1Handler, AddTwoHandler, Add2Handler, LoopDoneHandler и LDoneHandler.

В результате в методах FactorialHandler, Factorial1Handler, AddTwoHandler и LoopDoneHandler код остаться не должен. Он должен быть перемещен в соответствующие новые методы.

  1. Для асинхронного вызова методов вызовите метод 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});

}

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


  1. Сохраните результаты работы.

  2. Проверьте решение, выбрав команду Начать отладку
    в меню Отладка.

    1. Введите в текстовое поле значение 10000000 и нажмите кнопку Выполнить цикл.

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

    1. Быстро нажмите подряд все три кнопки, которые пока доступны. Все кнопки отреагируют на ввод данных. Первым должен появиться результат в метке под кнопкой Прибавить два. Следующими появятся результаты в метках под кнопками факториалов. Результатом в этих случаях будет бесконечность, поскольку число, возвращаемое при вычислении факториала для 10 000 000, слишком велико для хранения в переменной с двойной точностью. Затем после некоторой задержки появятся результаты под кнопкой с надписью Выполнить цикл.

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

http://i.msdn.microsoft.com/hash/030c41d9079671d09a62d8e2c1db6973.gif Координирование потоков

Опытный пользователь многопоточных приложений может заметить во введенном коде небольшие ошибки. Рассмотрим вновь следующие строки кода, имеющиеся в каждом методе вычислений в файле 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 следует применять к объекту, который возвращает ссылку, а не значение. Выполнение может затем продолжиться в виде блока, защищенного от воздействия со стороны других потоков. Набор операторов, которые выполняются как единый блок, называется атомарным. При появлении знака } выражение освобождается и потоки могут продолжать работу.

Чтобы добавить оператор "lock" в приложение, выполните следующие действия.


  1. Откройте файл Calculator.cs
    в Редакторе кода.

  2. Найдите каждый экземпляр следующего кода:

varTotalCalculations += 1;

varTotalAsOfNow = varTotalCalculations;

Должно присутствовать четыре экземпляра этого кода, по одному для каждого метода вычислений.

  1. Измените этот код следующим образом.

lock(this)

{

varTotalCalculations += 1;

varTotalAsOfNow = varTotalCalculations;

}

  1. Сохраните результаты работы и проверьте их, как в предыдущем примере.

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


<< предыдущая страница