Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Многопоточность.doc
Скачиваний:
4
Добавлен:
28.10.2018
Размер:
225.79 Кб
Скачать

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

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

WinForms

Пока что наиболее распространенным в .NET Framework API для создания пользовательского интерфейса для настольных приложения является WinForms. На мой взгляд, со временем его вытеснит WPF, но пока до этого еще далеко. Начнем же наше рассмотрение взаимодействия потоков с пользовательским интерфейсом в WinForms-приложениях (программа ThreadingWinFormsSynchronization).

Метод Invoke

Наиболее распространенным способом выполнить свой код гарантировано в основном потоке является использование метода Invoke класса Control. Этот метод гарантированно исполняет код переданного ему делегата в том потоке, в котором был создан соответствующий элемент 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);

}

}

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

Использование SynchronizationContext

Как я уже говорил, метод Invoke является наиболее распространенным способом синхронизации пользовательского интерфейса с отдельным потоком. Однако у него есть недостаток, иногда ограничивающий его применимость. Дело в том, что для его работы элемент, у которого он вызывается, должен быть отображен на экране. Говоря более строго, у него должен быть действительный (рабочий, валидный) handle окна. Т.е. элемент не только должен быть создан конструктором, но и отображен. Это не всегда возможно. В данном случае на помощь приходит класс SynchronizationContext (программа ThreadingWinFormsSynchronization). Получить объект этого класса очень просто. Для этого используется его статическое свойство 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

);

Использование анонимных методов позволяет избавиться от необходимости писать отдельные методы и передавать им параметры. Это еще одно преимущество перед использованием метода Invoke. Ему передать анонимный метод не удастся.