В ходе моего обслуживания старого приложения, которое плохо нарушало правила обновления кросс-потоков в winforms, я создал следующий метод расширения как способ быстро исправить незаконные вызовы, когда я их обнаружил:
/// <summary>
/// Execute a method on the control 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 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);
Я не сомневаюсь в полезности этого метода для исправления незаконных вызовов в устаревшем коде, но как насчет нового кода?
Хорошо ли использовать этот метод для обновления пользовательского интерфейса в части нового программного обеспечения, если вы не знаете, какой поток пытается обновить ui, или если новый код Winforms обычно содержит специальный выделенный метод с соответствующим Invoke()
связанная сантехника для всех таких обновлений пользовательского интерфейса? (Сначала я попытаюсь использовать другие соответствующие методы обработки фона, например, BackgroundWorker.)
Интересно, что это не сработает для ToolStripItems. Я только недавно обнаружил, что они происходят непосредственно из Component вместо Control. Вместо этого следует использовать содержащий ToolStrip
invoke.
Последующие комментарии:
Некоторые комментарии предполагают, что:
if (uiElement.InvokeRequired)
должен быть:
if (uiElement.InvokeRequired && uiElement.IsHandleCreated)
Рассмотрим следующую msdn documentation:
Это означает, что InvokeRequired может вернуть false, если Invoke не требуется (вызов происходит в том же потоке), или , если элемент управления был создан на другой поток, но контроль дескриптор еще не создан.
В случае, когда ручка управления еще не создано, вы должны не просто вызывать свойства, методы, или событий на элементе управления. Это может вызвать ручку управления созданный на фоне потока, изолирование элемента управления по потоку без насоса сообщений и приложение нестабильно.
Вы можете защитить от этого случая также проверяя значение IsHandleCreated, когда InvokeRequired возвращает false в фоновом потоке.
Если элемент управления был создан в другом потоке, но дескриптор элемента управления еще не создан, InvokeRequired
возвращает значение false. Это означает, что если InvokeRequired
возвращает true
, IsHandleCreated
всегда будет true. Повторное тестирование снова является избыточным и неправильным.