Как мне обновить GUI из другого потока?
Какой самый простой способ обновить Label
из другого потока?
у меня есть Form
на thread1
и с этого я начинаю другую ветку (thread2
). В то время как thread2
обрабатывает некоторые файлы, которые я хотел бы обновить Label
на Form
с текущим статусом thread2
работа
Как я могу это сделать?
45 ответов
Для.NET 2.0, вот хороший кусок кода, который я написал, который делает именно то, что вы хотите, и работает для любого свойства на Control
:
private delegate void SetControlPropertyThreadSafeDelegate(
Control control,
string propertyName,
object propertyValue);
public static void SetControlPropertyThreadSafe(
Control control,
string propertyName,
object propertyValue)
{
if (control.InvokeRequired)
{
control.Invoke(new SetControlPropertyThreadSafeDelegate
(SetControlPropertyThreadSafe),
new object[] { control, propertyName, propertyValue });
}
else
{
control.GetType().InvokeMember(
propertyName,
BindingFlags.SetProperty,
null,
control,
new object[] { propertyValue });
}
}
Назовите это так:
// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);
Если вы используете.NET 3.0 или выше, вы можете переписать указанный выше метод как метод расширения Control
класс, который бы упростил вызов:
myLabel.SetPropertyThreadSafe("Text", status);
ОБНОВЛЕНИЕ 05/10/2010:
Для.NET 3.0 вы должны использовать этот код:
private delegate void SetPropertyThreadSafeDelegate<TResult>(
Control @this,
Expression<Func<TResult>> property,
TResult value);
public static void SetPropertyThreadSafe<TResult>(
this Control @this,
Expression<Func<TResult>> property,
TResult value)
{
var propertyInfo = (property.Body as MemberExpression).Member
as PropertyInfo;
if (propertyInfo == null ||
!@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
@this.GetType().GetProperty(
propertyInfo.Name,
propertyInfo.PropertyType) == null)
{
throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
}
if (@this.InvokeRequired)
{
@this.Invoke(new SetPropertyThreadSafeDelegate<TResult>
(SetPropertyThreadSafe),
new object[] { @this, property, value });
}
else
{
@this.GetType().InvokeMember(
propertyInfo.Name,
BindingFlags.SetProperty,
null,
@this,
new object[] { value });
}
}
который использует LINQ и лямбда-выражения для обеспечения более чистого, простого и безопасного синтаксиса:
myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile
Теперь не только имя свойства проверяется во время компиляции, но и тип свойства, поэтому невозможно (например) присвоить строковое значение логическому свойству и, следовательно, вызвать исключение времени выполнения.
К сожалению, это не мешает никому делать глупости, такие как переход в другое Control
Это свойство и значение, поэтому с радостью скомпилируем следующее:
myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);
Поэтому я добавил проверки во время выполнения, чтобы убедиться, что переданное свойство действительно принадлежит Control
что метод вызывается. Не идеально, но все же намного лучше, чем версия.NET 2.0.
Если у кого-то есть какие-либо дальнейшие предложения о том, как улучшить этот код для обеспечения безопасности во время компиляции, пожалуйста, оставьте комментарий!
Самый простой способ - это анонимный метод, переданный в Label.Invoke
:
// Running on the worker thread
string newText = "abc";
form.Label.Invoke((MethodInvoker)delegate {
// Running on the UI thread
form.Label.Text = newText;
});
// Back on the worker thread
Заметить, чтоInvoke
блокирует выполнение до его завершения - это синхронный код. Вопрос не касается асинхронного кода, но в Stack Overflow содержится много информации о написании асинхронного кода, когда вы хотите узнать о нем.
Обработка долгой работы
Начиная с .NET 4.5 и C# 5.0, вы должны использовать Асинхронный шаблон на основе задач (TAP) вместе с асинхронными- ожидать ключевые слова во всех областях (включая GUI):
TAP - рекомендуемый шаблон асинхронного проектирования для новой разработки
вместо модели асинхронного программирования (APM) и асинхронного шаблона на основе событий (EAP) (последний включает класс BackgroundWorker).
Тогда рекомендуемое решение для новой разработки:
Асинхронная реализация обработчика событий (да, вот и все):
private async void Button_Clicked(object sender, EventArgs e) { var progress = new Progress<string>(s => label.Text = s); await Task.Factory.StartNew(() => SecondThreadConcern.LongWork(progress), TaskCreationOptions.LongRunning); label.Text = "completed"; }
Реализация второго потока, который уведомляет поток пользовательского интерфейса:
class SecondThreadConcern { public static void LongWork(IProgress<string> progress) { // Perform a long running work... for (var i = 0; i < 10; i++) { Task.Delay(500).Wait(); progress.Report(i.ToString()); } } }
Обратите внимание на следующее:
- Короткий и чистый код, написанный последовательным образом без обратных вызовов и явных потоков.
- Задача вместо темы.
- Ключевое слово async, которое позволяет использовать await, что, в свою очередь, препятствует достижению обработчиком события состояния завершения до завершения задачи и в то же время не блокирует поток пользовательского интерфейса.
- Класс Progress (см. Интерфейс IProgress), поддерживающий принцип разработки Разделение проблем (SoC) и не требующий явного диспетчера и вызова. Он использует текущий SynchronizationContext из своего места создания (здесь поток пользовательского интерфейса).
- TaskCreationOptions.LongRunning, который намекает не ставить задачу в очередь в ThreadPool.
Более подробные примеры см.: "Будущее C#": хорошие вещи приходят к тем, кто "ждет" Джозефа Албахари.
Смотрите также о концепции UI Threading Model.
Обработка исключений
Приведенный ниже фрагмент кода является примером того, как обрабатывать исключения и кнопки переключения. Enabled
свойство для предотвращения нескольких кликов во время фонового выполнения.
private async void Button_Click(object sender, EventArgs e)
{
button.Enabled = false;
try
{
var progress = new Progress<string>(s => button.Text = s);
await Task.Run(() => SecondThreadConcern.FailingWork(progress));
button.Text = "Completed";
}
catch(Exception exception)
{
button.Text = "Failed: " + exception.Message;
}
button.Enabled = true;
}
class SecondThreadConcern
{
public static void FailingWork(IProgress<string> progress)
{
progress.Report("I will fail in...");
Task.Delay(500).Wait();
for (var i = 0; i < 3; i++)
{
progress.Report((3 - i).ToString());
Task.Delay(500).Wait();
}
throw new Exception("Oops...");
}
}
Вариация простейшего решения Марка Гравелла для.NET 4:
control.Invoke((MethodInvoker) (() => control.Text = "new text"));
Или используйте вместо этого делегат Action:
control.Invoke(new Action(() => control.Text = "new text"));
Смотрите здесь для сравнения двух: MethodInvoker vs Action for Control.BeginInvoke
Запустить и забыть метод расширения для.NET 3.5+
using System;
using System.Windows.Forms;
public static class ControlExtensions
{
/// <summary>
/// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread.
/// </summary>
/// <param name="control"></param>
/// <param name="code"></param>
public static void UIThread(this Control @this, Action code)
{
if (@this.InvokeRequired)
{
@this.BeginInvoke(code);
}
else
{
code.Invoke();
}
}
}
Это можно вызвать с помощью следующей строки кода:
this.UIThread(() => this.myLabel.Text = "Text Goes Here");
Это классический способ сделать это:
using System;
using System.Windows.Forms;
using System.Threading;
namespace Test
{
public partial class UIThread : Form
{
Worker worker;
Thread workerThread;
public UIThread()
{
InitializeComponent();
worker = new Worker();
worker.ProgressChanged += new EventHandler<ProgressChangedArgs>(OnWorkerProgressChanged);
workerThread = new Thread(new ThreadStart(worker.StartWork));
workerThread.Start();
}
private void OnWorkerProgressChanged(object sender, ProgressChangedArgs e)
{
// Cross thread - so you don't get the cross-threading exception
if (this.InvokeRequired)
{
this.BeginInvoke((MethodInvoker)delegate
{
OnWorkerProgressChanged(sender, e);
});
return;
}
// Change control
this.label1.Text = e.Progress;
}
}
public class Worker
{
public event EventHandler<ProgressChangedArgs> ProgressChanged;
protected void OnProgressChanged(ProgressChangedArgs e)
{
if(ProgressChanged!=null)
{
ProgressChanged(this,e);
}
}
public void StartWork()
{
Thread.Sleep(100);
OnProgressChanged(new ProgressChangedArgs("Progress Changed"));
Thread.Sleep(100);
}
}
public class ProgressChangedArgs : EventArgs
{
public string Progress {get;private set;}
public ProgressChangedArgs(string progress)
{
Progress = progress;
}
}
}
В вашем рабочем потоке есть событие. Ваш поток пользовательского интерфейса запускает другой поток, чтобы выполнить работу, и подключает это рабочее событие, чтобы вы могли отобразить состояние рабочего потока.
Затем в пользовательском интерфейсе вам нужно пересечь потоки, чтобы изменить фактический элемент управления... как метку или индикатор выполнения.
Простое решение заключается в использовании Control.Invoke
,
void DoSomething()
{
if (InvokeRequired) {
Invoke(new MethodInvoker(updateGUI));
} else {
// Do Something
updateGUI();
}
}
void updateGUI() {
// update gui here
}
Подавляющее большинство ответов используют Control.Invoke
что является состоянием гонки, ожидающим случиться. Например, рассмотрим принятый ответ:
string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate {
someLabel.Text = newText; // runs on UI thread
});
Если пользователь закрывает форму непосредственно перед this.Invoke
называется (помните, this
это Form
объект), ObjectDisposedException
будет, вероятно, уволен.
Решение заключается в использовании SynchronizationContext
конкретно SynchronizationContext.Current
как предполагает hamilton.danielb (другие ответы зависят от конкретных SynchronizationContext
реализации, что совершенно ненужно). Я бы немного изменил его код для использования SynchronizationContext.Post
скорее, чем SynchronizationContext.Send
хотя (поскольку обычно рабочему потоку не нужно ждать):
public partial class MyForm : Form
{
private readonly SynchronizationContext _context;
public MyForm()
{
_context = SynchronizationContext.Current
...
}
private MethodOnOtherThread()
{
...
_context.Post(status => someLabel.Text = newText,null);
}
}
Обратите внимание, что на.NET 4.0 и выше вы действительно должны использовать задачи для асинхронных операций. Смотрите ответ n-san для эквивалентного подхода на основе задач (используя TaskScheduler.FromCurrentSynchronizationContext
).
Наконец, на.NET 4.5 и выше вы также можете использовать Progress<T>
(который в основном захватывает SynchronizationContext.Current
после его создания), как продемонстрировано Рышардом Деганом, для случаев, когда длительная операция должна запускать код пользовательского интерфейса во время работы.
Код многопоточности часто глючит и всегда трудно проверить. Вам не нужно писать многопоточный код для обновления пользовательского интерфейса из фоновой задачи. Просто используйте класс BackgroundWorker для запуска задачи и его метод ReportProgress для обновления пользовательского интерфейса. Обычно вы просто сообщаете, что процент выполнения завершен, но есть еще одна перегрузка, которая включает объект состояния. Вот пример, который просто сообщает о строковом объекте:
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(5000);
backgroundWorker1.ReportProgress(0, "A");
Thread.Sleep(5000);
backgroundWorker1.ReportProgress(0, "B");
Thread.Sleep(5000);
backgroundWorker1.ReportProgress(0, "C");
}
private void backgroundWorker1_ProgressChanged(
object sender,
ProgressChangedEventArgs e)
{
label1.Text = e.UserState.ToString();
}
Это хорошо, если вы всегда хотите обновить одно и то же поле. Если вам нужно сделать более сложные обновления, вы можете определить класс для представления состояния пользовательского интерфейса и передать его в метод ReportProgress.
И последнее, не забудьте установить WorkerReportsProgress
флаг или ReportProgress
метод будет полностью проигнорирован.
Вы должны убедиться, что обновление происходит в правильном потоке; поток пользовательского интерфейса.
Для этого вам придется вызывать обработчик событий, а не вызывать его напрямую.
Вы можете сделать это, подняв ваше событие следующим образом:
(Код набран здесь из моей головы, поэтому я не проверял правильный синтаксис и т. Д., Но он должен помочь вам.)
if( MyEvent != null )
{
Delegate[] eventHandlers = MyEvent.GetInvocationList();
foreach( Delegate d in eventHandlers )
{
// Check whether the target of the delegate implements
// ISynchronizeInvoke (Winforms controls do), and see
// if a context-switch is required.
ISynchronizeInvoke target = d.Target as ISynchronizeInvoke;
if( target != null && target.InvokeRequired )
{
target.Invoke (d, ... );
}
else
{
d.DynamicInvoke ( ... );
}
}
}
Обратите внимание, что приведенный выше код не будет работать в проектах WPF, поскольку элементы управления WPF не реализуют ISynchronizeInvoke
интерфейс.
Чтобы убедиться, что приведенный выше код работает с Windows Forms и WPF и всеми другими платформами, вы можете взглянуть на AsyncOperation
, AsyncOperationManager
а также SynchronizationContext
классы.
Чтобы легко вызывать события таким образом, я создал метод расширения, который позволяет мне упростить вызов события, просто вызвав:
MyEvent.Raise(this, EventArgs.Empty);
Конечно, вы также можете использовать класс BackGroundWorker, который будет абстрагироваться от вас.
Из-за тривиальности сценария у меня фактически был бы опрос потока пользовательского интерфейса для статуса. Я думаю, вы найдете, что это может быть довольно элегантно.
public class MyForm : Form
{
private volatile string m_Text = "";
private System.Timers.Timer m_Timer;
private MyForm()
{
m_Timer = new System.Timers.Timer();
m_Timer.SynchronizingObject = this;
m_Timer.Interval = 1000;
m_Timer.Elapsed += (s, a) => { MyProgressLabel.Text = m_Text; };
m_Timer.Start();
var thread = new Thread(WorkerThread);
thread.Start();
}
private void WorkerThread()
{
while (...)
{
// Periodically publish progress information.
m_Text = "Still working...";
}
}
}
Этот подход позволяет избежать операции маршалинга, необходимой при использовании ISynchronizeInvoke.Invoke
а также ISynchronizeInvoke.BeginInvoke
методы. Нет ничего плохого в использовании техники маршалинга, но есть несколько предостережений, о которых нужно знать.
- Убедитесь, что вы не звоните
BeginInvoke
слишком часто, или это может привести к переполнению насоса сообщений. - призвание
Invoke
на рабочем потоке есть блокирующий вызов. Это временно остановит работу, выполняемую в этой теме.
Стратегия, которую я предлагаю в этом ответе, меняет коммуникационные роли потоков. Вместо того, чтобы рабочий поток выдвигал данные, поток пользовательского интерфейса опрашивает их. Это общий шаблон, используемый во многих сценариях. Поскольку все, что вы хотите сделать, это отображать информацию о ходе выполнения из рабочего потока, то я думаю, вы обнаружите, что это решение является отличной альтернативой маршалинг-решению. Он имеет следующие преимущества.
- Пользовательский интерфейс и рабочие потоки остаются слабо связанными, в отличие от
Control.Invoke
или жеControl.BeginInvoke
подход, который тесно связывает их. - Поток пользовательского интерфейса не будет препятствовать продвижению рабочего потока.
- Рабочий поток не может доминировать во время, которое поток пользовательского интерфейса тратит на обновление.
- Интервалы, с которыми пользовательский интерфейс и рабочие потоки выполняют операции, могут оставаться независимыми.
- Рабочий поток не может переполнить насос сообщений потока пользовательского интерфейса.
- Поток пользовательского интерфейса определяет, когда и как часто пользовательский интерфейс обновляется.
Вам нужно будет вызвать метод в потоке GUI. Вы можете сделать это, вызвав Control.Invoke.
Например:
delegate void UpdateLabelDelegate (string message);
void UpdateLabel (string message)
{
if (InvokeRequired)
{
Invoke (new UpdateLabelDelegate (UpdateLabel), message);
return;
}
MyLabelControl.Text = message;
}
В предыдущих ответах не требуется ничего из Invoke.
Вам нужно взглянуть на WindowsFormsSynchronizationContext:
// In the main thread
WindowsFormsSynchronizationContext mUiContext = new WindowsFormsSynchronizationContext();
...
// In some non-UI Thread
// Causes an update in the GUI thread.
mUiContext.Post(UpdateGUI, userData);
...
void UpdateGUI(object userData)
{
// Update your GUI controls here
}
Это похоже на решение выше с использованием.NET Framework 3.0, но оно решает проблему обеспечения безопасности во время компиляции.
public static class ControlExtension
{
delegate void SetPropertyValueHandler<TResult>(Control souce, Expression<Func<Control, TResult>> selector, TResult value);
public static void SetPropertyValue<TResult>(this Control source, Expression<Func<Control, TResult>> selector, TResult value)
{
if (source.InvokeRequired)
{
var del = new SetPropertyValueHandler<TResult>(SetPropertyValue);
source.Invoke(del, new object[]{ source, selector, value});
}
else
{
var propInfo = ((MemberExpression)selector.Body).Member as PropertyInfo;
propInfo.SetValue(source, value, null);
}
}
}
Использовать:
this.lblTimeDisplay.SetPropertyValue(a => a.Text, "some string");
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, false);
Компилятор потерпит неудачу, если пользователь передаст неверный тип данных.
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");
Salvete! Поискав этот вопрос, я обнаружил, что ответы FrankG и Oregon Ghost были самыми простыми и полезными для меня. Теперь я кодирую в Visual Basic и запускаю этот фрагмент через конвертер; так что я не совсем понимаю, как это получается.
У меня есть диалоговая форма под названием form_Diagnostics,
который имеет поле richtext, называется updateDiagWindow,
который я использую в качестве своего рода отображения журнала. Мне нужно было иметь возможность обновить его текст из всех тем. Дополнительные строки позволяют окну автоматически прокручиваться до самых новых строк.
Итак, теперь я могу обновить отображение одной строкой из любой точки всей программы так, как вы думаете, она будет работать без каких-либо потоков:
form_Diagnostics.updateDiagWindow(whatmessage);
Основной код (поместите его в код класса вашей формы):
#region "---------Update Diag Window Text------------------------------------"
// This sub allows the diag window to be updated by all threads
public void updateDiagWindow(string whatmessage)
{
var _with1 = diagwindow;
if (_with1.InvokeRequired) {
_with1.Invoke(new UpdateDiagDelegate(UpdateDiag), whatmessage);
} else {
UpdateDiag(whatmessage);
}
}
// This next line makes the private UpdateDiagWindow available to all threads
private delegate void UpdateDiagDelegate(string whatmessage);
private void UpdateDiag(string whatmessage)
{
var _with2 = diagwindow;
_with2.appendtext(whatmessage);
_with2.SelectionStart = _with2.Text.Length;
_with2.ScrollToCaret();
}
#endregion
Label lblText; //initialized elsewhere
void AssignLabel(string text)
{
if (InvokeRequired)
{
BeginInvoke((Action<string>)AssignLabel, text);
return;
}
lblText.Text = text;
}
Обратите внимание, что BeginInvoke()
предпочтительнее Invoke()
потому что это менее вероятно, вызовет взаимоблокировки (однако, это не проблема здесь, просто назначая текст метке):
Когда используешь Invoke()
Вы ждете, когда метод вернется. Теперь может случиться так, что вы делаете что-то в вызываемом коде, который должен будет ожидать поток, что может быть неочевидно, если он скрыт в некоторых вызываемых вами функциях, что само по себе может происходить косвенно через обработчики событий. Таким образом, вы будете ждать нити, нить будет ждать вас, и вы зашли в тупик.
Это фактически привело к зависанию некоторых из наших выпущенных программ. Это было достаточно легко исправить, заменив Invoke()
с BeginInvoke()
, Если вам не нужна синхронная операция, которая может иметь место, если вам нужно возвращаемое значение, используйте BeginInvoke()
,
Когда я столкнулся с той же проблемой, я обратился за помощью к Google, но вместо того, чтобы дать мне простое решение, он больше смутил меня, приведя примеры MethodInvoker
и бла-бла-бла. Поэтому я решил решить это самостоятельно. Вот мое решение:
Сделайте делегата следующим образом:
Public delegate void LabelDelegate(string s);
void Updatelabel(string text)
{
if (label.InvokeRequired)
{
LabelDelegate LDEL = new LabelDelegate(Updatelabel);
label.Invoke(LDEL, text);
}
else
label.Text = text
}
Вы можете вызвать эту функцию в новом потоке, как это
Thread th = new Thread(() => Updatelabel("Hello World"));
th.start();
Не путайся с Thread(() => .....)
, Я использую анонимную функцию или лямбда-выражение, когда я работаю над потоком. Чтобы уменьшить количество строк кода, вы можете использовать ThreadStart(..)
метод, который я не должен здесь объяснять.
Для многих целей это так просто:
public delegate void serviceGUIDelegate();
private void updateGUI()
{
this.Invoke(new serviceGUIDelegate(serviceGUI));
}
"serviceGUI ()" - это метод уровня GUI внутри формы (this), который может изменять столько элементов управления, сколько вы хотите. Вызовите updateGUI() из другого потока. Параметры могут быть добавлены для передачи значений или (возможно, быстрее) использования переменных области класса с блокировками для них по мере необходимости, если существует какая-либо вероятность столкновения между потоками, обращающимися к ним, что может вызвать нестабильность. Используйте BeginInvoke вместо Invoke, если поток без графического интерфейса критичен ко времени (с учетом предупреждения Брайана Гидеона).
Это в моем C# 3.0 варианте решения Яна Кемпа:
public static void SetPropertyInGuiThread<C,V>(this C control, Expression<Func<C, V>> property, V value) where C : Control
{
var memberExpression = property.Body as MemberExpression;
if (memberExpression == null)
throw new ArgumentException("The 'property' expression must specify a property on the control.");
var propertyInfo = memberExpression.Member as PropertyInfo;
if (propertyInfo == null)
throw new ArgumentException("The 'property' expression must specify a property on the control.");
if (control.InvokeRequired)
control.Invoke(
(Action<C, Expression<Func<C, V>>, V>)SetPropertyInGuiThread,
new object[] { control, property, value }
);
else
propertyInfo.SetValue(control, value, null);
}
Вы называете это так:
myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
- Он добавляет нулевую проверку к результату "as MemberExpression".
- Это улучшает статическую безопасность типов.
В противном случае оригинал - очень хорошее решение.
Большинство других ответов немного сложны для меня по этому вопросу (я новичок в C#), поэтому я пишу свои:
У меня есть приложение WPF, и я определил работника, как показано ниже:
Выпуск:
BackgroundWorker workerAllocator;
workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1) {
// This is my DoWork function.
// It is given as an anonymous function, instead of a separate DoWork function
// I need to update a message to textbox (txtLog) from this thread function
// Want to write below line, to update UI
txt.Text = "my message"
// But it fails with:
// 'System.InvalidOperationException':
// "The calling thread cannot access this object because a different thread owns it"
}
Решение:
workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1)
{
// The below single line works
txtLog.Dispatcher.BeginInvoke((Action)(() => txtLog.Text = "my message"));
}
Мне еще предстоит выяснить, что означает приведенная выше строка, но она работает.
Для WinForms:
Решение:
txtLog.Invoke((MethodInvoker)delegate
{
txtLog.Text = "my message";
});
Просто используйте что-то вроде этого:
this.Invoke((MethodInvoker)delegate
{
progressBar1.Value = e.ProgressPercentage; // runs on UI thread
});
Вы можете использовать уже существующий делегат Action
:
private void UpdateMethod()
{
if (InvokeRequired)
{
Invoke(new Action(UpdateMethod));
}
}
Моя версия - вставить одну строку рекурсивной "мантры":
Без аргументов:
void Aaaaaaa()
{
if (InvokeRequired) { Invoke(new Action(Aaaaaaa)); return; } //1 line of mantra
// Your code!
}
Для функции, которая имеет аргументы:
void Bbb(int x, string text)
{
if (InvokeRequired) { Invoke(new Action<int, string>(Bbb), new[] { x, text }); return; }
// Your code!
}
ЭТО ЭТО.
Некоторая аргументация: обычно читаемость кода ставит {} после if ()
Выписка в одну строку. Но в данном случае это обычная все та же "мантра". Это не нарушает читабельность кода, если этот метод согласован в проекте. И это спасает ваш код от мусора (одна строка кода вместо пяти).
Как вы видите if(InvokeRequired) {something long}
Вы просто знаете, что "эта функция безопасна для вызова из другого потока".
И еще одно общее расширение Control Control..
Сначала добавьте метод расширения для объектов типа Control
public static void InvokeIfRequired<T>(this T c, Action<T> action) where T : Control
{
if (c.InvokeRequired)
{
c.Invoke(new Action(() => action(c)));
}
else
{
action(c);
}
}
и вызовите это из другого потока, чтобы получить доступ к элементу управления с именем object1 в потоке пользовательского интерфейса:
object1.InvokeIfRequired(c => { c.Visible = true; });
object1.InvokeIfRequired(c => { c.Text = "ABC"; });
.. или вот так
object1.InvokeIfRequired(c =>
{
c.Text = "ABC";
c.Visible = true;
}
);
Создайте переменную класса:
SynchronizationContext _context;
Установите его в конструкторе, который создает ваш пользовательский интерфейс:
var _context = SynchronizationContext.Current;
Когда вы хотите обновить ярлык:
_context.Send(status =>{
// UPDATE LABEL
}, null);
Вы должны использовать invoke и делегировать
private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
Попробуйте обновить ярлык, используя это
public static class ExtensionMethods
{
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement)
{
uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
}
}
Самый простой способ в приложениях WPF:
this.Dispatcher.Invoke((Action)(() =>
{
// This refers to a form in a WPF application
val1 = textBox.Text; // Access the UI
}));
Когда вы находитесь в потоке пользовательского интерфейса, вы можете запросить у него планировщик задач контекста синхронизации. Это даст вам TaskScheduler, который планирует все в потоке пользовательского интерфейса.
Затем вы можете связать свои задачи так, чтобы, когда результат был готов, другая задача (которая запланирована в потоке пользовательского интерфейса) выбрала ее и присвоила метке.
public partial class MyForm : Form
{
private readonly TaskScheduler _uiTaskScheduler;
public MyForm()
{
InitializeComponent();
_uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
private void buttonRunAsyncOperation_Click(object sender, EventArgs e)
{
RunAsyncOperation();
}
private void RunAsyncOperation()
{
var task = new Task<string>(LengthyComputation);
task.ContinueWith(antecedent =>
UpdateResultLabel(antecedent.Result), _uiTaskScheduler);
task.Start();
}
private string LengthyComputation()
{
Thread.Sleep(3000);
return "47";
}
private void UpdateResultLabel(string text)
{
labelResult.Text = text;
}
}
Это работает для задач (не потоков), которые сейчас являются предпочтительным способом написания параллельного кода.
Даже если операция занимает много времени (thread.sleep в моем примере) - этот код НЕ заблокирует ваш пользовательский интерфейс:
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(ThreadJob));
t.IsBackground = true;
t.Start();
}
private void ThreadJob()
{
string newValue= "Hi";
Thread.Sleep(2000);
this.Invoke((MethodInvoker)delegate
{
label1.Text = newValue;
});
}