В ходе обслуживания старого приложения, которое сильно нарушало правила межпотокового обновления в winforms, я создал следующий метод расширения для быстрого исправления недопустимых вызовов при обнаружении:
/// <summary>
/// Execute a method on the control's owning thread.
/// </summary>
/// <param name="uiElement">The control that is being updated.</param>
/// <param name="updater">The method that updates uiElement.</param>
/// <param name="forceSynchronous">True to force synchronous execution of
/// updater. False to allow asynchronous execution if the call is marshalled
/// from a non-GUI thread. If the method is called on the GUI thread,
/// execution is always synchronous.</param>
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
if (uiElement == null)
{
throw new ArgumentNullException("uiElement");
}
if (uiElement.InvokeRequired)
{
if (forceSynchronous)
{
uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
else
{
uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
}
else
{
if (!uiElement.IsHandleCreated)
{
// Do nothing if the handle isn't created already. The user's responsible
// for ensuring that the handle they give us exists.
return;
}
if (uiElement.IsDisposed)
{
throw new ObjectDisposedException("Control is already disposed.");
}
updater();
}
}
Пример использования:
this.lblTimeDisplay.SafeInvoke(() => this.lblTimeDisplay.Text = this.task.Duration.ToString(), false);
Мне также нравится, как я могу использовать замыкания для чтения, хотя forceSynchronous должен быть истинным в этом случае:
string taskName = string.Empty;
this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true);
Я не подвергаю сомнению полезность этого метода для исправления незаконных вызовов в унаследованном коде, но как насчет нового кода?
Разумно ли использовать этот метод для обновления пользовательского интерфейса в части нового программного обеспечения, когда вы можете не знать, какой поток пытается обновить пользовательский интерфейс, или если новый код Winforms обычно содержит определенный, выделенный метод с соответствующим Invoke()
-связанная сантехника для всех таких обновлений пользовательского интерфейса? (Конечно, сначала я попытаюсь использовать другие подходящие методы фоновой обработки, например, BackgroundWorker.)
Интересно, что это не сработает для ToolStripItems . Я только недавно обнаружил, что они происходят от Component вместо Control . Вместо этого следует использовать содержащий ToolStrip
invoke.
Продолжение комментариев:
Некоторые комментарии предполагают, что:
if (uiElement.InvokeRequired)
должно быть:
if (uiElement.InvokeRequired && uiElement.IsHandleCreated)
Рассмотрим следующую документацию по MSDN :
Это означает, что InvokeRequired может
вернуть false , если Invoke не требуется
(вызов происходит в том же потоке),
или , если элемент управления был создан на
другой поток, но контроль
дескриптор еще не создан.
В случае, когда ручка управления
еще не был создан, вы должны
не просто вызывать свойства, методы,
или события на контроле. Это может
заставить ручку управления быть
созданный в фоновом потоке,
изолировать контроль на потоке
без сообщения насосом и делая
Приложение нестабильно.
Вы можете защитить от этого случая,
также проверяет значение
IsHandleСоздано, когда InvokeRequired
возвращает false в фоновом потоке.
Если элемент управления был создан в другом потоке, но дескриптор элемента еще не был создан, InvokeRequired
возвращает значение false. Это означает, что если InvokeRequired
возвращает true
, IsHandleCreated
всегда будет истинным. Повторное тестирование является излишним и неправильным.