Думаю, что стоит привести ещё один пример, на этот раз - как использовать интерфейс ISyncronizeInvoke. Этот интерфейс реализован в классе System.Windows.Forms.Control и предназначен специально для прокидывания любых делегатов в поток обработки цикла сообщений, в случае с контролами - цикла сообщений Windows.
Интерфейс содержит 4 члена.
Свойство InvokeRequired возвращает true, если текущий поток выполнения не является потоком обработки цикла сообщений. Если мы уже в потоке обработки цикла сообщений, свойство возвращает false.
Метод Invoke добавляет переданный ему делегат в цикл обработки сообщений и блокирует текущий поток до тех пор, пока цикл обработки сообщений не выполнит этот делегат. После того как делегат выполнен, метод возвращает управление со значением, которое вернул переданный ему делегат в процессе выполнения. Если делегат имел тип возвращаемого значения void, метод возвращает null.
Метод BeginInvoke добавляет переданный ему в качестве параметра делегат в цикл обработки сообщений и немедленно возвращает управление. Он возвращает объект типа IAsyncResult, который можно передать в качестве параметра последнему методу EndInvoke для ожидания завершения асинхронной операции. Простейший цикл обработки сообщений, который я привёл в предыдущем примере, обрабатывает события именно асинхронно, возвращая управление, не дожидаясь выполнения добавленного в очередь метода.
Для того чтобы добавить элемент в коллекцию так, чтобы само добавление обязательно происходило в потоке пользовательского интерфейса вне зависимости от того, откуда оно было вызвано, следует использовать такой паттерн:
private readonly ISynchronizeInvoke m_invoker;
private readonly BindingList<MyEntry> m_entries = new BindingList<MyEntry>();
// Конструктор с Dependency Injection, которому передаётся контрол либо форма
public BackgroundHandler(ISynchronizeInvoke invoker)
{
if (invoker == null)
throew new ArgumentNullException("invoker");
m_invoker = invoker;
}
//
public bool Add(MyEntry entry)
{
// Если мы не в потоке обработки сообщений,
// метод добавляет в очередь сообщений ссылку на самого себя,
// ждёт завершения обработки и возвращает управление
if (m_invoker.InvokeRequired)
return (bool)invoker.Invoke(new Func<MyEntry, bool>(this.Add), new object[] { entry });
// Поскольку мы сюда попали, мы уже в потоке обработки сообщений,
// делаем полезную работу - добавляем переданный элемент в список.
// Возвращаемое значение - чисто для демонстрации, как его возвращать
m_entries.Add(entry);
return true;
}
Помещение кода синхронизации прямо в начало метода перед полезной работой очень облегчает читабельность кода, если полезная работа содержит много логики.