Избегайте беды Invoke / BeginInvoke в обработке событий в WinForm для кросс-streamов?

Я по-прежнему сталкивается с фоновым streamом в пользовательском интерфейсе WinForm. Зачем? Вот некоторые из проблем:

  1. Очевидно, что самая важная проблема, я не могу изменить элемент управления, если я не выполняю тот же stream, который его создал.
  2. Как вы знаете, Invoke, BeginInvoke и т. Д. Недоступны до тех пор, пока не будет создан элемент управления.
  3. Даже после того, как RequiresInvoke возвращает true, BeginInvoke все равно может отбрасывать ObjectDisposed и даже если он не бросает, он никогда не сможет выполнить код, если элемент управления уничтожается.
  4. Даже после того, как RequiresInvoke возвращает true, Invoke может бесконечно зависать в ожидании выполнения с помощью элемента управления, который был удален одновременно с вызовом Invoke.

Я ищу элегантное решение этой проблемы, но прежде чем я узнаю, что я ищу, я подумал, что я проясню проблему. Это должно принять общую проблему и поставить для нее более конкретный пример. Для этого примера предположим, что мы передаем большие объемы данных через Интернет. Пользовательский интерфейс должен иметь возможность отображать диалог прогресса для уже запущенной передачи. Диалог прогресса должен обновляться постоянно и быстро (обновляется от 5 до 20 раз в секунду). Пользователь может в любое время отклонить диалог прогресса и снова вызвать его, если это необходимо. И далее, давайте притворяемся аргументами, что если диалог виден, он должен обработать каждое событие прогресса. Пользователь может нажать «Отмена» в диалоговом окне выполнения и путем изменения аргументов событий, отменив операцию.

Теперь мне нужно решение, которое будет соответствовать следующему полю ограничений:

  1. Разрешить рабочему streamу вызывать метод в элементе управления / форме и блокировать / дожидаться завершения выполнения.
  2. Разрешить самому диалогу вызывать этот же метод при инициализации или тому подобное (и, следовательно, не использовать invoke).
  3. Не применяйте бремя реализации по методу обработки или вызывающему событию, решение должно только изменить сама подписка на события.
  4. Соответственно обрабатывать блокировку вызывает диалог, который может находиться в процессе утилизации. К сожалению, это не так просто, как проверка IsDisposed.
  5. Должно быть возможно использовать любой тип события (предположим делегат типа EventHandler)
  6. Не следует переводить исключения в TargetInvocationException.
  7. Решение должно работать с .Net 2.0 и выше

Итак, можно ли это решить с учетом ограничений выше? Я искал и вырыл бесчисленные блоги и дискуссии, и, увы, я все еще с пустыми armми.

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

Обновление № 2: Хорошо, я попытаюсь описать проблему немного подробнее и посмотреть, что (если что-либо) вытряхнет. Следующие свойства, которые позволяют нам определить его состояние, имеют пару вещей, вызывающих беспокойство …

  1. Control.InvokeRequired = Документировано, чтобы возвращать false, если выполняется в текущем streamе, или если IsHandleCreated возвращает false для всех родителей. Я смущен реализацией InvokeRequired, имеющей возможность либо бросить ObjectDisposedException, либо потенциально даже воссоздать дескриптор объекта. И поскольку InvokeRequired может возвращать true, когда мы не можем вызвать (Dispose in progress), и он может возвращать false, даже если нам может понадобиться использовать invoke (Create in progress), это просто не может быть доверено во всех случаях. Единственный случай, когда я могу увидеть, где мы можем доверять InvokeRequired return false, – это когда IsHandleCreated возвращает true как до, так и после вызова (BTW, документы MSDN для InvokeRequired указывают на проверку для IsHandleCreated).

  2. Control.IsHandleCreated = Возвращает true, если дескриптор был назначен элементу управления; в противном случае – false. Хотя IsHandleCreated – безопасный вызов, он может разбиться, если элемент управления находится в процессе воссоздания его дескриптора. Эта потенциальная проблема, по-видимому, может быть решена путем выполнения блокировки (контроля) при доступе к IsHandleCreated и InvokeRequired.

  3. Control.Disposing = Возвращает true, если элемент управления находится в процессе утилизации.

  4. Control.IsDisposed = Возвращает true, если элемент управления удален. Я рассматриваю возможность подписки на событие Disposed и проверку свойства IsDisposed, чтобы определить, будет ли BeginInvoke когда-либо завершаться. Большая проблема здесь заключается в отсутствии блокировки синхронизации, которая препятствует переносу Disposing -> Disposed. Возможно, если вы подписаны на событие Disposed и после этого убедитесь, что Disposing == false && IsDisposed == false, вы все равно можете никогда не видеть огонь Disposed event. Это связано с тем, что реализация Dispose sets Disposing = false, а затем устанавливает Disposed = true. Это дает вам возможность (хотя и небольшую), чтобы читать как Disposing, так и IsDisposed как ложные на удаленном элементе управления.

… у меня болит голова 🙁 Надеюсь, вышеприведенная информация проливает немного больше света на проблемы для тех, кто испытывает эти проблемы. Я ценю ваши лишние мыслительные циклы.

Закрытие проблемы … Следующая половина метода Control.DestroyHandle ():

if (!this.RecreatingHandle && (this.threadCallbackList != null)) { lock (this.threadCallbackList) { Exception exception = new ObjectDisposedException(base.GetType().Name); while (this.threadCallbackList.Count > 0) { ThreadMethodEntry entry = (ThreadMethodEntry) this.threadCallbackList.Dequeue(); entry.exception = exception; entry.Complete(); } } } if ((0x40 & ((int) ((long) UnsafeNativeMethods.GetWindowLong(new HandleRef(this.window, this.InternalHandle), -20)))) != 0) { UnsafeNativeMethods.DefMDIChildProc(this.InternalHandle, 0x10, IntPtr.Zero, IntPtr.Zero); } else { this.window.DestroyHandle(); } 

Вы заметите, что ObjectDisposedException отправляется во все вызовы с перекрестными streamами. Вскоре после этого вызывается вызов this.window.DestroyHandle (), который, в свою очередь, уничтожает окно и устанавливает его ссылку на обращение к IntPtr.Zero, тем самым предотвращая дальнейшие вызовы в методе BeginInvoke (или, точнее, MarshaledInvoke, которые обрабатывают как BeginInvoke, так и Invoke). Проблема здесь в том, что после того, как релиз блокировки на threadCallbackList, новая запись может быть вставлена ​​до того, как stream Control обработает дескриптор windows. Кажется, это так, что я вижу, хотя и нечасто, достаточно часто, чтобы остановить выпуск.

Обновление № 4:

Извините, что продолжайте перетаскивать это; однако, я думал, что стоит документировать здесь. Мне удалось решить большинство проблем выше, и я сужусь над решением, которое работает. Я ударил еще одну проблему, о которой я беспокоился, но до сих пор не видел «in-the-wild».

Эта проблема связана с гением, который написал свойство Control.Handle:

  public IntPtr get_Handle() { if ((checkForIllegalCrossThreadCalls && !inCrossThreadSafeCall) && this.InvokeRequired) { throw new InvalidOperationException(SR.GetString("IllegalCrossThreadCall", new object[] { this.Name })); } if (!this.IsHandleCreated) { this.CreateHandle(); } return this.HandleInternal; } 

Это само по себе не так плохо (независимо от моего мнения о получении {} модификаций); однако в сочетании с свойством InvokeRequired или методом Invoke / BeginInvoke это плохо. Вот основной stream Invoke:

 if( !this.IsHandleCreated ) throw; ... do more stuff PostMessage( this.Handle, ... ); 

Проблема здесь в том, что из другого streamа я могу успешно передать первый оператор if, после которого дескриптор уничтожается streamом элемента управления, что приводит к тому, что get свойства Handle заново создает дескриптор windows в моем streamе. Это может привести к возникновению исключения в streamе исходного элемента управления. На самом деле, я действительно смущен, потому что нет никакого способа защититься от этого. Если бы они использовали свойство InternalHandle и тестировались на результат IntPtr.Zero, это не было бы проблемой.

Ваш сценарий, как описано, аккуратно подходит к BackgroundWorker – почему бы просто не использовать это? Ваши требования к решению слишком универсальны и, скорее, необоснованны – я сомневаюсь, что есть решение, которое удовлетворит их всех.

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

Сообщение в блоге по теме:

Хорошо, через несколько дней я закончил создание решения. Он решает все перечисленные ограничения и цели в первоначальном сообщении. Использование прост и прямолинейно:

 myWorker.SomeEvent += new EventHandlerForControl(this, myWorker_SomeEvent).EventHandler; 

Когда рабочий stream вызывает это событие, он будет обрабатывать требуемый вызов в stream управления. Он гарантирует, что он не будет вешать бесконечно и будет последовательно бросать ObjectDisposedException, если он не может выполнить в streamе управления. Я создал другие определения classа, один для игнорирования ошибки, а другой – для прямого вызова делегата, если элемент управления недоступен. Появляется, чтобы хорошо работать и полностью проходит несколько тестов, которые воспроизводят вышеперечисленные проблемы. Существует только одна проблема с решением, которое я не могу предотвратить, не нарушая ограничение № 3 выше. Эта проблема является последней (Обновление № 4) в описании проблемы, проблемы с streamами в Get Handle. Это может привести к неожиданному поведению в исходном streamе управления, и я регулярно видел InvalidOperationException (), вызывая при вызове Dispose (), поскольку дескриптор в процессе создания в моем streamе. Чтобы разрешить это, я гарантирую блокировку доступа к функциям, которые будут использовать свойство Control.Handle. Это позволяет форме перегрузить метод DestroyHandle и блокировать до вызова базовой реализации. Если это будет сделано, этот class должен быть полностью streamобезопасным (насколько мне известно).

 public class Form : System.Windows.Forms.Form { protected override void DestroyHandle() { lock (this) base.DestroyHandle(); } } 

Вы можете заметить, что основной аспект решения мертвого замка стал петлей опроса. Первоначально я успешно решил тестовые примеры, обработав событие управления для Disposed и HandleDestroyed и используя несколько дескрипторов ожидания. После более тщательного обзора я обнаружил, что подписка / отказ от подписки на эти события не являются streamобезопасными. Таким образом, я решил вместо этого опросить IsHandleCreated, чтобы не создавать ненужные противоречия в событиях streamа и тем самым избежать возможности создания состояния блокировки.

Во всяком случае, вот решение, которое я придумал:

 ///  /// Provies a wrapper type around event handlers for a control that are safe to be /// used from events on another thread. If the control is not valid at the time the /// delegate is called an exception of type ObjectDisposedExcpetion will be raised. ///  [System.Diagnostics.DebuggerNonUserCode] public class EventHandlerForControl where TEventArgs : EventArgs { ///  The control who's thread we will use for the invoke  protected readonly Control _control; ///  The delegate to invoke on the control  protected readonly EventHandler _delegate; ///  /// Constructs an EventHandler for the specified method on the given control instance. ///  public EventHandlerForControl(Control control, EventHandler handler) { if (control == null) throw new ArgumentNullException("control"); _control = control.TopLevelControl; if (handler == null) throw new ArgumentNullException("handler"); _delegate = handler; } ///  /// Constructs an EventHandler for the specified delegate converting it to the expected /// EventHandler<TEventArgs> delegate type. ///  public EventHandlerForControl(Control control, Delegate handler) { if (control == null) throw new ArgumentNullException("control"); _control = control.TopLevelControl; if (handler == null) throw new ArgumentNullException("handler"); //_delegate = handler.Convert>(); _delegate = handler as EventHandler; if (_delegate == null) { foreach (Delegate d in handler.GetInvocationList()) { _delegate = (EventHandler) Delegate.Combine(_delegate, Delegate.CreateDelegate(typeof(EventHandler), d.Target, d.Method, true) ); } } if (_delegate == null) throw new ArgumentNullException("_delegate"); } ///  /// Used to handle the condition that a control's handle is not currently available. This /// can either be before construction or after being disposed. ///  protected virtual void OnControlDisposed(object sender, TEventArgs args) { throw new ObjectDisposedException(_control.GetType().Name); } ///  /// This object will allow an implicit cast to the EventHandler<T> type for easier use. ///  public static implicit operator EventHandler(EventHandlerForControl instance) { return instance.EventHandler; } ///  /// Handles the 'magic' of safely invoking the delegate on the control without producing /// a dead-lock. ///  public void EventHandler(object sender, TEventArgs args) { bool requiresInvoke = false, hasHandle = false; try { lock (_control) // locked to avoid conflicts with RecreateHandle and DestroyHandle { if (true == (hasHandle = _control.IsHandleCreated)) { requiresInvoke = _control.InvokeRequired; // must remain true for InvokeRequired to be dependable hasHandle &= _control.IsHandleCreated; } } } catch (ObjectDisposedException) { requiresInvoke = hasHandle = false; } if (!requiresInvoke && hasHandle) // control is from the current thread { _delegate(sender, args); return; } else if (hasHandle) // control invoke *might* work { MethodInvokerImpl invocation = new MethodInvokerImpl(_delegate, sender, args); IAsyncResult result = null; try { lock (_control)// locked to avoid conflicts with RecreateHandle and DestroyHandle result = _control.BeginInvoke(invocation.Invoker); } catch (InvalidOperationException) { } try { if (result != null) { WaitHandle handle = result.AsyncWaitHandle; TimeSpan interval = TimeSpan.FromSeconds(1); bool complete = false; while (!complete && (invocation.MethodRunning || _control.IsHandleCreated)) { if (invocation.MethodRunning) complete = handle.WaitOne();//no need to continue polling once running else complete = handle.WaitOne(interval); } if (complete) { _control.EndInvoke(result); return; } } } catch (ObjectDisposedException ode) { if (ode.ObjectName != _control.GetType().Name) throw;// *likely* from some other source... } } OnControlDisposed(sender, args); } ///  /// The class is used to take advantage of a special-case in the Control.InvokeMarshaledCallbackDo() /// implementation that allows us to preserve the exception types that are thrown rather than doing /// a delegate.DynamicInvoke(); ///  [System.Diagnostics.DebuggerNonUserCode] private class MethodInvokerImpl { readonly EventHandler _handler; readonly object _sender; readonly TEventArgs _args; private bool _received; public MethodInvokerImpl(EventHandler handler, object sender, TEventArgs args) { _received = false; _handler = handler; _sender = sender; _args = args; } public MethodInvoker Invoker { get { return this.Invoke; } } private void Invoke() { _received = true; _handler(_sender, _args); } public bool MethodRunning { get { return _received; } } } } 

Если вы видите что-то не так, пожалуйста, дайте мне знать.

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

Архитектура Invoke / BeginInvoke просто выполняет предоставленный делегат в streamе пользовательского интерфейса BeginInvoke управления, отправляя ему сообщение Windows, и сам цикл сообщений выполняет делегат. Конкретные действия этого не имеют значения, но дело в том, что нет особой причины, по которой вам нужно использовать эту архитектуру для синхронизации streamов с streamом пользовательского интерфейса. Все, что вам нужно, это какой-то другой цикл, например, в Forms.Timer или что-то в этом роде, который контролирует Queue для делегатов для выполнения и делает это. Было бы довольно просто реализовать свои собственные, хотя я не знаю, что конкретно вы получите для вас, что Invoke и BeginInvoke не предоставляют.

Это не ответ на вторую часть вопроса, но я включу его только для справки:

 private delegate object SafeInvokeCallback(Control control, Delegate method, params object[] parameters); public static object SafeInvoke(this Control control, Delegate method, params object[] parameters) { if (control == null) throw new ArgumentNullException("control"); if (control.InvokeRequired) { IAsyncResult result = null; try { result = control.BeginInvoke(new SafeInvokeCallback(SafeInvoke), control, method, parameters); } catch (InvalidOperationException) { /* This control has not been created or was already (more likely) closed. */ } if (result != null) return control.EndInvoke(result); } else { if (!control.IsDisposed) return method.DynamicInvoke(parameters); } return null; } 

Этот код должен избегать наиболее распространенных ошибок с Invoke / BeginInvoke, и он прост в использовании. Просто поверните

 if (control.InvokeRequired) control.Invoke(...) else ... 

в

 control.SafeInvoke(...) 

Подобная конструкция возможна для BeginInvoke.

Вау, длинный вопрос. Я попытаюсь организовать свой ответ, чтобы вы могли исправить меня, если я понял что-то не так, хорошо?

1) Если у вас нет оснований для вызова методов пользовательского интерфейса непосредственно из разных streamов, не делайте этого. Вы всегда можете выбрать модель производителя / потребителя с помощью обработчиков событий:

 protected override void OnLoad() { //... component.Event += new EventHandler(myHandler); } protected override void OnClosing() { //... component.Event -= new EventHandler(myHandler); } 

myHandler будет запущен каждый раз, когда компонент в другом streamе должен выполнить что-то в пользовательском интерфейсе, например. Кроме того, настройка обработчика событий в OnLoad и отмена подписки в OnClosing гарантирует, что события будут получать / обрабатывать только пользовательский интерфейс, пока его дескриптор создан и готов к обработке событий. Вы даже не сможете запускать события в этом диалоговом окне, если оно находится в процессе утилизации, потому что вы больше не будете подписываться на это событие. Если другое событие будет запущено, пока все еще обрабатывается, оно будет поставлено в очередь.

Вы можете передать всю необходимую информацию в аргументах событий: обновляете ли вы прогресс, закрываете окно и т. Д.

2) Вам не требуется InvokeRequired, если вы используете модель, предложенную выше. В этом примере вы знаете, что единственное, что срабатывает myHandler, будет ваш компонент, который, например, живет в другом streamе.

 private void myHandler(object sender, EventArgs args) { BeginInvoke(Action(myMethod)); } 

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

3) Остерегайтесь синхронных вызовов. Если вы хотите, вы можете заменить использование Invoke вместо BeginInvoke. Это заблокирует ваш компонент до тех пор, пока событие не будет обработано. Однако, если в пользовательском интерфейсе вам нужно общаться с чем-то, что является исключительным для streamа, в котором живет ваш компонент, вы можете иметь проблемы с блокировкой. (Я не знаю, ясно ли я, пожалуйста, дайте мне знать). У меня были проблемы с исключениями при использовании отражения (TargetInvocationException) и BeginInvoke (поскольку они запускают другой stream, вы теряете часть трассировки стека), но я не помню, чтобы было много проблем с вызовами Invoke, поэтому вы должны быть безопасным, когда дело доходит до исключений.

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

Я пытаюсь организовать все такие призывные сообщения в GUI как огонь и забыть (обработка исключения, которое графический интерфейс может вызывать из-за состояния гонки при утилизации формы).

Таким образом, если он никогда не исполнится никакого вреда, это делается.

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

Это довольно сложный вопрос. Как я упоминаю в комментарии, я не думаю, что это разрешимо с учетом документированных ограничений. Вы можете взломать его, учитывая конкретную реализацию инфраструктуры .net: знание реализации различных функций-членов может помочь вам обмануть, захватив блокировки здесь и там, и зная, что «на самом деле OKAY, вызывать другие функции-члены в другом streamе. ”

Итак, мой основной ответ на данный момент: «Нет». Я ненавижу говорить, что это невозможно, потому что я очень верю в инфраструктуру .Net. Кроме того, я сравнительно новичок, не изучая frameworks вообще, или CS, но интернет открыт (даже для таких невежественных людей, как я)!

По другой теме аргумент может быть поставлен и хорошо поддержан: «Вам никогда не потребуется Invoke , используйте только BeginInvoke , и BeginInvoke и забывайте». Я не буду пытаться его поддержать или даже сказать, что это правильное утверждение, но я скажу, что общая реализация неверна и представляет собой рабочий (надеюсь).

Вот общая реализация (взята из другого ответа здесь):

 protected override void OnLoad() { //... component.Event += new EventHandler(myHandler); } protected override void OnClosing() { //... component.Event -= new EventHandler(myHandler); } 

Это не является streamобезопасным. Компонент мог легко начать вызов списка вызовов непосредственно перед отменой подписки, и только после того, как мы закончим удаление, обработчик будет вызван. Реальным моментом является то, что он не документировал, как каждый компонент должен использовать механизм событий в .Net, и, честно говоря, он не должен отписывать вас вообще: как только вы выдали свой номер телефона, никто не должен его стереть!

Лучше:

 protected override void OnLoad(System.EventArgs e) { component.Event += new System.EventHandler(myHandler); } protected override void OnFormClosing(FormClosedEventArgs e) { component.Event -= new System.EventHandler(myHandler); lock (lockobj) { closing = true; } } private void Handler(object a, System.EventArgs e) { lock (lockobj) { if (closing) return; this.BeginInvoke(new System.Action(HandlerImpl)); } } /*Must be called only on GUI thread*/ private void HandlerImpl() { this.Hide(); } private readonly object lockobj = new object(); private volatile bool closing = false; 

Пожалуйста, дайте мне знать, если я что-то пропустил.

Если вам не нравится BackgroundWoker (как описано в @Pavel), вы можете посмотреть эту библиотеку http://www.wintellect.com/PowerThreading.aspx .

Если я это понимаю, почему вам когда-нибудь нужно было отменить диалог прогресса во время работы приложения? Почему бы просто не показывать и не скрывать его по запросу пользователя? Похоже, это сделает вашу проблему хотя бы немного проще.

Почему бы не просто скрыть диалог, когда пользователь его отклонит? Это должно работать нормально, если вы не показываете этот диалог модально. (используйте show вместо showdialog). Я считаю, что вы можете сохранить свой диалог прогресса поверх своего собственного windows (если вам нужно), передав хост в диалог при вызове show.

Использование System.ComponentModel.ISynchronizeInvoke приятно при создании System.ComponentModel.Component , например BackgroundWorker . Следующий fragment кода – это то, как FileSystemWater обрабатывает события.

  '''  ''' Gets or sets the object used to marshal the event handler calls issued as a result of finding a file in a search. '''   _ Public Property SynchronizingObject() As System.ComponentModel.ISynchronizeInvoke Get If (_synchronizingObject Is Nothing) AndAlso (MyBase.DesignMode) Then Dim oHost As IDesignerHost = DirectCast(MyBase.GetService(GetType(IDesignerHost)), IDesignerHost) If (Not (oHost Is Nothing)) Then Dim oRootComponent As Object = oHost.RootComponent If (Not (oRootComponent Is Nothing)) AndAlso (TypeOf oRootComponent Is ISynchronizeInvoke) Then _synchronizingObject = DirectCast(oRootComponent, ISynchronizeInvoke) End If End If End If Return _synchronizingObject End Get Set(ByVal Value As System.ComponentModel.ISynchronizeInvoke) _synchronizingObject = Value End Set End Property Private _onStartupHandler As EventHandler Protected Sub OnStartup(ByVal e As EventArgs) If ((Not Me.SynchronizingObject Is Nothing) AndAlso Me.SynchronizingObject.InvokeRequired) Then Me.SynchronizingObject.BeginInvoke(_onStartupHandler, New Object() {Me, e}) Else _onStartupHandler.Invoke(Me, e) End If End Sub 

Вот что я сейчас использую. Он основан на использовании SynchronizationContext и вдохновлен статьей блога JaredPar – см. Его ответ выше. Это может быть не идеально, но это позволяет избежать некоторых проблем с OP, которые я также испытывал.

  // Homemade Action-style delegates to provide .Net 2.0 compatibility, since .Net 2.0 does not // include a non-generic Action delegate nor Action delegates with more than one generic type // parameter. (The DMethodWithOneParameter definition is not needed, could be Action // instead, but is defined for consistency.) Some interesting observations can be found here: // http://geekswithblogs.net/BlackRabbitCoder/archive/2011/11/03/c.net-little-wonders-the-generic-action-delegates.aspx public delegate void DMethodWithNoParameters(); public delegate void DMethodWithOneParameter(T parameter1); public delegate void DMethodWithTwoParameters(T1 parameter1, T2 parameter2); public delegate void DMethodWithThreeParameters(T1 parameter1, T2 parameter2, T3 parameter3); ///  /// Class containing support code to use the SynchronizationContext mechanism to dispatch the /// execution of a method to the WinForms UI thread, from another thread. This can be used as an /// alternative to the Control.BeginInvoke() mechanism which can be problematic under certain /// conditions. See for example the discussion here: /// http://stackoverflow.com/questions/1364116/avoiding-the-woes-of-invoke-begininvoke-in-cross-thread-winform-event-handling /// /// As currently coded this works with methods that take zero, one, two or three arguments, but /// it is a trivial job to extend the code for methods taking more arguments. ///  public class WinFormsHelper { // An arbitrary WinForms control associated with thread 1, used to check that thread-switching // with the SynchronizationContext mechanism should be OK private readonly Control _thread1Control = null; // SynchronizationContext for the WinForms environment's UI thread private readonly WindowsFormsSynchronizationContext _synchronizationContext; ///  /// Constructor. This must be called on the WinForms UI thread, typically thread 1. (Unless /// running under the Visual Studio debugger, then the thread number is arbitrary.) /// /// The provided "thread 1 control" must be some WinForms control that will remain in /// existence for as long as this object is going to be used, for example the main Form /// control for the application. ///  /// see above public WinFormsHelper(Control thread1Control) { _thread1Control = thread1Control; if (thread1Control.InvokeRequired) throw new Exception("Not called on thread associated with WinForms controls."); _synchronizationContext = SynchronizationContext.Current as WindowsFormsSynchronizationContext; if (_synchronizationContext == null) // Should not be possible? throw new Exception("SynchronizationContext.Current = null or wrong type."); } // The following BeginInvoke() methods follow a boilerplate pattern for how these methods // should be implemented - they differ only in the number of arguments that the caller wants // to provide. public void BeginInvoke(DMethodWithNoParameters methodWithNoParameters) { _synchronizationContext.Post((object stateNotUsed) => { if (!_thread1Control.IsDisposed) methodWithNoParameters(); }, null); } public void BeginInvoke(DMethodWithOneParameter methodWithOneParameter, T parameter1) { _synchronizationContext.Post((object stateNotUsed) => { if (!_thread1Control.IsDisposed) methodWithOneParameter(parameter1); }, null); } public void BeginInvoke(DMethodWithTwoParameters methodWithTwoParameters, T1 parameter1, T2 parameter2) { _synchronizationContext.Post((object stateNotUsed) => { if (!_thread1Control.IsDisposed) methodWithTwoParameters(parameter1, parameter2); }, null); } public void BeginInvoke(DMethodWithThreeParameters methodWithThreeParameters, T1 parameter1, T2 parameter2, T3 parameter3) { _synchronizationContext.Post((object stateNotUsed) => { if (!_thread1Control.IsDisposed) methodWithThreeParameters(parameter1, parameter2, parameter3); }, null); } } 
Interesting Posts
Давайте будем гением компьютера.