
- •Многопоточность
- •Оглавление
- •Задачи многопоточности
- •Методы создания потоков
- •Делегаты
- •Ожидание завершения работы потока
- •Получение результата работы метода, выполнявшегося в отдельном потоке
- •Полезный совет
- •Класс Thread
- •Ожидание завершения потока
- •Управление выполнением потока
- •«Сон» потока
- •Приоритет потоков
- •Фоновые потоки и потоки «переднего плана»
- •Класс ThreadPool
- •Класс Task
- •Создание потоков
- •Ожидание завершения потока
- •Получение результатов выполнения потока
- •Прерывание потока
- •Класс Parallel
- •Метод AsParallel()
- •Синхронизация потоков
- •Оператор lock
- •Класс ReaderWriterLock
- •Класс Mutex
- •Класс WaitHandle
- •Класс AutoResetEvent
- •Класс ManualResetEvent
- •Блокировка потоков
- •Взаимодествие с пользовательским интерфейсом
- •Метод Invoke
- •Использование SynchronizationContext
- •Класс BackgroundWorker
- •Окончание работы и возвращение результата
- •Прогресс выполнения
- •Отмена выполнения метода
- •Объект Dispatcher
- •Класс BackgroundWorker
- •Заключение
Взаимодествие с пользовательским интерфейсом
Последним вопросом, который нам необходимо обсудить, является взаимодействие отдельного потока с пользовательским интерфейсом. На самом деле графический интерфейс пользователя (GUI) может исполняться только в том же потоке, в котором был создан. О том, как этого добиться, и рассказывает эта часть статьи.
WinForms
Несмотря на стремительное наступление технологии WPF, все еще много приложений пишется на WinForms. Начнем же наше рассмотрение взаимодействия потоков с пользовательским интерфейсом в WinForms-приложениях (проект WindowForms).
Метод Invoke
Наиболее распространенным способом выполнить свой код гарантировано в основном потоке является использование метода Invoke класса Control (форма Invoke). Этот метод гарантированно исполняет код переданного ему делегата в том потоке, в котором был создан соответствующий элемент Control:
Func<long> dlgt = new Func<long>(GetCheckNumber);
return (long) this.Invoke( dlgt );
Просто оберните вызов вашего метода в делегат и передайте его методу Invoke. Кроме того, класс Control имеет свойство InvokeRequired, позволяющий узнать, нужно ли вызывать для вашего коде Invoke, или можно выполнять его напрямую. В связи с этим полный код метода, который должен выполняться в потоке пользовательского интерфейса, выглядит следующим образом:
private long GetCheckNumber()
{
if (this.InvokeRequired)
{
Func<long> dlgt = new Func<long>(GetCheckNumber);
return (long) this.Invoke( dlgt );
}
else
{
return long.Parse(tbNumber.Text);
}
}
Следует отметить, что вызов таких методов из вашего рабочего потока не должен быть слишком частым, поскольку в этом случае окажется, что вы постоянно нагружаете поток пользовательского интерфейса и у него не остается времени на свои задачи (например, на отрисовку). Это будет эквивалентно выполнению всего вашего кода в одном потоке, чего мы хотели избежать. Кроме того, слишком частый вызов Invoke замедляет выполнение вашего потока.
Использование SynchronizationContext
Как я уже говорил, метод Invoke является наиболее распространенным способом синхронизации пользовательского интерфейса с отдельным потоком. Однако у него есть недостаток, иногда ограничивающий его применимость. Дело в том, что для его работы элемент, у которого он вызывается, должен быть отображен на экране. Говоря более строго, у него должен быть действительный (рабочий, валидный) handle окна. Т.е. элемент не только должен быть создан конструктором, но и отображен. Это не всегда возможно. В данном случае на помощь приходит класс SynchronizationContext (форма SynchronizationContext). Получить объект этого класса очень просто. Для этого используется его статическое свойство Current. Оно возвращает синхронизационный контекст потока. Однако знайте, что у потока может не быть синхронизационного контекста. Поэтому это свойство может возвращать и null. Однако метод Application.Run , запускающий WinForms–приложение, всегда устанавливает этот контекст для потока пользовательского интерфейса. Поэтому вы можете безопасно получить его, обычно это делается по событию загрузки главной формы:
private void MainForm_Load(object sender, EventArgs e)
{
m_SyncContext = SynchronizationContext.Current;
}
Вы должны сохранить ссылку на синхронизационный контекст, чтобы он был доступен в вашем отдельном потоке.
Синхронизационный контекст имеет 2 основных метода: Send и Post. Обоим передается делегат SendOrPostCallback и параметр для него. Первый выполняет переданный ему делегат синхронно (блокируя вызвавший метод Send поток до тех пор, пока делегат не будет исполнен), а второй – асинхронно.
Применение этих методов может выглядеть примерно таким образом:
m_SyncContext.Send(
new SendOrPostCallback(
delegate
{
pb.Minimum = 0;
pb.Maximum = 100;
pb.Value = 0;
btnCheck.Enabled = false;
}
),
null
);
Использование анонимных методов позволяет избавиться от необходимости писать отдельные методы и передавать им параметры.