Как мне обновить GUI из другого потока?

Какой самый простой способ обновить Labelиз другого Thread?

  • У меня Formработает thread1, и с этого я запускаю другой поток ( thread2).

  • Хотя thread2обрабатывают некоторые файлы , которые я хотел бы обновить Labelна Formс текущим статусом thread2работы «S.

Как я мог это сделать?

19.03.2009 09:37:40
Разве .net 2.0+ не имеет класса BackgroundWorker только для этого. Это пользовательский интерфейс потока в курсе. 1. Создайте BackgroundWorker 2. Добавьте двух делегатов (один для обработки и один для завершения)
Preet Sangha 19.03.2009 10:30:29
может быть немного поздно: codeproject.com/KB/cs/Threadsafe_formupdating.aspx
MichaelD 19.01.2010 14:15:40
См. Ответ для .NET 4.5 и C # 5.0: stackoverflow.com/a/18033198/2042090
Ryszard Dżegan 3.08.2013 13:21:36
Этот вопрос не относится к Gtk # GUI. Для Gtk # см. Этот и этот ответ.
hlovdal 1.04.2014 21:52:54
Осторожно: ответы на этот вопрос теперь являются беспорядочным беспорядком OT («вот что я сделал для своего приложения WPF») и исторических артефактов .NET 2.0.
Marc L. 11.07.2018 19:48:29
30 ОТВЕТОВ
РЕШЕНИЕ

Для .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.

Если у кого-то есть какие-либо дальнейшие предложения о том, как улучшить этот код для безопасности во время компиляции, пожалуйста, прокомментируйте!

767
3.03.2015 00:52:09
Есть случаи, когда this.GetType () оценивается так же, как propertyInfo.ReflectedType (например, LinkLabel на WinForms). У меня нет большого опыта C #, но я думаю, что условие для исключения должно быть следующим: if (propertyInfo == null || (!@this.GetType (). IsSubclassOf (propertyInfo.ReflectedType) && @ this.GetType ( )! = propertyInfo.ReflectedType) || @ this.GetType (). GetProperty (propertyInfo.Name, propertyInfo.PropertyType) == null)
Corvin 31.01.2011 13:03:20
@lan это SetControlPropertyThreadSafe(myLabel, "Text", status)можно вызвать из другого модуля, класса или формы
Smith 23.06.2011 10:58:31
Предоставленное решение является излишне сложным. Посмотрите решение Марка Гравелла или решение Зейда Масуда, если вы цените простоту.
Frank Hileman 28.03.2014 17:23:09
Это решение тратит массу ресурсов, если вы обновляете несколько свойств, так как каждый Invoke стоит больших ресурсов. Я не думаю, что именно так была задумана особенность Thread Safety. Делайте Encapsulte свои действия по обновлению пользовательского интерфейса и вызывайте его ОДИН РАЗ (а не для каждого свойства)
Console 6.08.2014 12:09:30
С какой стати вы используете этот код поверх компонента BackgroundWorker?
Andy 8.10.2016 17:11:20

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

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

Вы можете сделать это, подняв ваше событие следующим образом:

(Код набран здесь из моей головы, поэтому я не проверил правильный синтаксис и т. Д., Но он должен помочь вам.)

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, который будет абстрагироваться от вас.

37
16.09.2017 10:05:49
Действительно, но я не люблю «загромождать» мой код GUI этим вопросом. Мой GUI не должен заботиться о том, нужно ли ему вызывать или нет. Другими словами: я не думаю, что именно GUI отвечает за выполнение context-swithc.
Frederik Gheysels 19.03.2009 09:51:40
Разбиение делегата и т. Д. Кажется излишним - почему бы не просто: SynchronizationContext.Current.Send (делегат {MyEvent (...);}, ноль);
Marc Gravell♦ 19.03.2009 11:07:47
У вас всегда есть доступ к SynchronizationContext? Даже если ваш класс находится в классе lib?
Frederik Gheysels 19.03.2009 11:42:57

Простое решение заключается в использовании Control.Invoke.

void DoSomething()
{
    if (InvokeRequired) {
        Invoke(new MethodInvoker(updateGUI));
    } else {
        // Do Something
        updateGUI();
    }
}

void updateGUI() {
    // update gui here
}
62
19.12.2015 10:02:10
хорошо сделано для простоты! не только просто, но и хорошо работает! Я действительно не понимаю, почему Microsoft не может сделать это проще, чем должно быть! для вызова 1 строки в главном потоке мы должны написать пару функций!
MBH 19.12.2015 09:17:43
@MBH Согласен. Кстати, вы заметили ответ выше stackoverflow.com/a/3588137/199364 , который определяет метод расширения? Сделайте это один раз в классе пользовательских утилит, и вам больше не нужно заботиться о том, чтобы Microsoft не делала этого за нас :)
ToolmakerSteve 16.05.2016 18:14:54
@ToolmakerSteve Это именно то, что он хотел! Вы правы, мы можем найти способ, но я имею в виду, с точки зрения СУХОЙ (не повторяйтесь), проблему, которая имеет общее решение, она может быть решена с минимальными усилиями Microsoft, что сэкономит много времени для программисты :)
MBH 16.05.2016 18:56:40

Вам нужно будет вызвать метод в потоке GUI. Вы можете сделать это, вызвав Control.Invoke.

Например:

delegate void UpdateLabelDelegate (string message);

void UpdateLabel (string message)
{
    if (InvokeRequired)
    {
         Invoke (new UpdateLabelDelegate (UpdateLabel), message);
         return;
    }

    MyLabelControl.Text = message;
}
29
19.03.2009 09:47:27
Строка invoke дает мне ошибку компилятора. Наилучшее совпадение перегруженного метода для 'System.Windows.Forms.Control.Invoke (System.Delegate, object [])' содержит недопустимые аргументы
CruelIO 19.03.2009 11:12:09

Самый простой способ - это анонимный метод, переданный в 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 содержится много информации о написании асинхронного кода, когда вы хотите узнать о нем.

1068
16.09.2017 09:20:29
Учитывая, что ОП не упомянул ни один класс / экземпляр, кроме формы, это неплохое значение по умолчанию ...
Marc Gravell♦ 19.03.2009 10:30:38
Не забывайте, что ключевое слово «this» ссылается на класс «Control».
AZ. 16.03.2010 19:49:15
@ codecompleting это в любом случае безопасно, и мы уже знаем, что работаем, так зачем проверять то, что мы знаем?
Marc Gravell♦ 1.12.2011 16:50:55
@ Dragouf не совсем - одна из причин использования этого метода заключается в том, что вы уже знаете, какие части выполняются на рабочем, а какие - на потоке пользовательского интерфейса. Не нужно проверять.
Marc Gravell♦ 16.02.2012 18:16:35
@ Joan.bdm, мне не хватает контекста, чтобы прокомментировать это
Marc Gravell♦ 30.07.2014 12:00:31

Это классический способ сделать это:

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;
        }
    }
}

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

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

66
26.03.2017 09:45:55

Для многих целей это так просто:

public delegate void serviceGUIDelegate();
private void updateGUI()
{
  this.Invoke(new serviceGUIDelegate(serviceGUI));
}

«serviceGUI ()» - это метод уровня GUI в форме (this), который может изменять столько элементов управления, сколько вы хотите. Вызовите updateGUI () из другого потока. Параметры могут быть добавлены для передачи значений или (возможно, быстрее) использования переменных области класса с блокировками для них по мере необходимости, если существует какая-либо вероятность столкновения между потоками, обращающимися к ним, что может вызвать нестабильность. Используйте BeginInvoke вместо Invoke, если поток без графического интерфейса критичен ко времени (помня о предупреждении Брайана Гидеона).

21
26.08.2010 03:17:44

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

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подход, который тесно связывает их.
  • Поток пользовательского интерфейса не будет препятствовать продвижению рабочего потока.
  • Рабочий поток не может доминировать во время, которое поток пользовательского интерфейса тратит на обновление.
  • Интервалы, с которыми пользовательский интерфейс и рабочие потоки выполняют операции, могут оставаться независимыми.
  • Рабочий поток не может переполнить насос сообщений потока пользовательского интерфейса.
  • Поток пользовательского интерфейса определяет, когда и как часто пользовательский интерфейс обновляется.
28
26.03.2017 09:48:03
Хорошая идея. Единственное, что вы не упомянули, это то, как вы правильно располагаете таймер после завершения WorkerThread. Обратите внимание, что это может вызвать проблемы, когда приложение заканчивается (т. Е. Пользователь закрывает приложение). У вас есть идея, как это решить?
Matt 11.11.2013 10:24:59
@Matt Вместо того чтобы использовать анонимный обработчик для Elapsedсобытия, вы используете метод member, чтобы вы могли удалить таймер при удалении формы ...
Phil1970 20.04.2018 00:54:08
@ Phil1970 - Хороший вопрос. Вы имели в виду, как System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };и назначив его через m_Timer.Elapsed += handler;, позже в контексте утилизации, m_Timer.Elapsed -= handler;я прав? И для утилизации / закрытия, следуя советам, которые обсуждались здесь .
Matt 20.04.2018 08:37:29

Потоковый код часто глючит и всегда трудно проверить. Вам не нужно писать многопоточный код для обновления пользовательского интерфейса из фоновой задачи. Просто используйте класс 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метод будет полностью проигнорирован.

47
22.09.2012 03:59:50
В конце обработки также возможно обновить пользовательский интерфейс через backgroundWorker1_RunWorkerCompleted.
DavidRR 1.04.2016 19:18:04

Запустите и забудьте метод расширения для .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");
137
27.08.2010 21:10:32
Какой смысл использования @this? Разве «контроль» не будет эквивалентен? Есть ли какие-то преимущества у @this?
argyle 9.09.2013 00:03:38
@jeromeyers - @thisпросто имя переменной, в данном случае ссылка на текущий элемент управления, вызывающий расширение. Вы можете переименовать его в источник, или что-то еще, что плавает на вашей лодке Я использую @this, потому что он ссылается на «этот элемент управления», который вызывает расширение и соответствует (по крайней мере, в моей голове) использованию ключевого слова «это» в обычном (не являющемся расширением) коде.
StyxRiver 12.09.2013 04:26:05
Это здорово, легко и для меня лучшее решение. Вы можете включить всю работу, которую вы должны сделать, в поток пользовательского интерфейса. Пример: this.UIThread (() => {txtMessage.Text = message; listBox1.Items.Add (message);});
Auto 23.02.2016 19:39:13
Мне очень нравится это решение. Незначительная гнида: я бы назвал этот метод, OnUIThreadа не UIThread.
ToolmakerSteve 16.05.2016 18:08:35
Вот почему я назвал это расширение RunOnUiThread. Но это только личный вкус.
Grisgram 30.01.2019 08:11:26

Это похоже на решение выше с использованием .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");
23
26.03.2017 09:56:51

Это в моем 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!")
  1. Он добавляет нулевую проверку к результату «as MemberExpression».
  2. Это улучшает статическую безопасность типов.

В противном случае оригинал - очень хорошее решение.

21
15.09.2011 20:40:57

Salvete! Поискав этот вопрос, я обнаружил, что ответы FrankG и Oregon Ghost были самыми простыми и полезными для меня. Теперь я кодирую в Visual Basic и запускаю этот фрагмент через конвертер; так что я не совсем уверен, как это получается.

У меня есть диалоговая форма, form_Diagnostics,которая называется richtext box, 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
23
26.03.2017 09:58:47

Моя версия - вставить одну строку рекурсивной «мантры»:

Без аргументов:

    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}вы просто знаете, что «эту функцию безопасно вызывать из другого потока».

14
26.03.2017 10:00:26
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().

21
8.03.2017 12:44:54

Вариация простейшего решения Марка Гравелла для .NET 4:

control.Invoke((MethodInvoker) (() => control.Text = "new text"));

Или используйте вместо этого делегат Action:

control.Invoke(new Action(() => control.Text = "new text"));

Смотрите здесь для сравнения двух: MethodInvoker vs Action for Control.BeginInvoke

236
23.05.2017 12:34:51
что такое «контроль» в этом примере? Мой интерфейс управления? Попытка реализовать это в WPF для элемента управления меткой, и Invoke не является членом моей метки.
Dbloom 9.02.2017 22:17:17
Как насчет метода расширения, такого как @styxriver stackoverflow.com/a/3588137/206730 ?
Kiquenet 3.04.2017 14:17:51
объявить «Действие у;» внутри класса или метода измените текстовое свойство и обновите текст с помощью этого фрагмента кода 'yourcontrol.Invoke (y = () => yourcontrol.Text = "new text");'
Antonio Leite 25.08.2017 15:20:12
@Dbloom это не участник, потому что это только для WinForms. Для WPF вы используете Dispatcher.Invoke
sLw 22.03.2018 13:09:42
Я следовал этому решению, но иногда мой пользовательский интерфейс не обновлялся. Я обнаружил, что мне нужно this.refresh()принудительно сделать недействительным и перекрасить GUI .. если это будет полезно ..
Rakibul Haq 5.04.2018 10:47:29

Вы можете использовать уже существующий делегат Action:

private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}
15
26.03.2017 10:01:11

Когда я столкнулся с той же проблемой, я обратился за помощью к 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(..)метод, который я не должен здесь объяснять.

20
24.04.2017 07:23:27

В предыдущих ответах не требуется ничего из 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
}
27
26.03.2017 10:06:14
Как вы думаете, что метод Post использует под капотом? :)
increddibelly 5.05.2016 09:09:23

Попробуйте обновить этикетку, используя это

public static class ExtensionMethods
{
    private static Action EmptyDelegate = delegate() { };

    public static void Refresh(this UIElement uiElement)
    {
        uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }
}
13
17.04.2014 09:56:54
Это для Windows Forms ?
Kiquenet 3.04.2017 14:13:41

Обработка долгой работы

Начиная с .NET 4.5 и C # 5.0, вы должны использовать Асинхронный шаблон на основе задач (TAP) вместе с асинхронными - ожидать ключевые слова во всех областях (включая GUI):

TAP - рекомендуемый шаблон асинхронного проектирования для новой разработки

вместо модели асинхронного программирования (APM) и асинхронного шаблона на основе событий (EAP) (последний включает класс BackgroundWorker ).

Тогда рекомендуемое решение для новой разработки:

  1. Асинхронная реализация обработчика событий (да, вот и все):

    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";
    }
  2. Реализация второго потока, который уведомляет поток пользовательского интерфейса:

    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());
            }
        }
    }

Обратите внимание на следующее:

  1. Короткий и чистый код, написанный последовательным образом без обратных вызовов и явных потоков.
  2. Задача вместо темы .
  3. Ключевое слово async , которое позволяет использовать await, что, в свою очередь, препятствует достижению обработчиком события состояния завершения до завершения задачи и в то же время не блокирует поток пользовательского интерфейса.
  4. Класс Progress (см. Интерфейс IProgress ), поддерживающий принцип разработки Разделение проблем (SoC) и не требующий явного диспетчера и вызова. Он использует текущий SynchronizationContext из своего места создания (здесь поток пользовательского интерфейса).
  5. TaskCreationOptions.LongRunning, который намекает не ставить задачу в очередь в ThreadPool .

Для более подробного примера смотрите: Будущее C #: Хорошие вещи приходят к тем , кто «Await» от Джозефа Albahari .

Смотрите также о концепции 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...");
    }
}
399
23.05.2017 11:55:02
Если SecondThreadConcern.LongWork()выдается исключение, может ли оно быть перехвачено потоком пользовательского интерфейса? Это отличный пост, кстати.
kdbanman 13.08.2015 17:53:40
Я добавил дополнительный раздел к ответу, чтобы выполнить ваши требования. С уважением.
Ryszard Dżegan 14.08.2015 08:14:55
Класс ExceptionDispatchInfo отвечает за это чудо повторного выброса фонового исключения в потоке пользовательского интерфейса в шаблоне async-await.
Ryszard Dżegan 18.08.2015 06:45:55
Неужели я думаю, что этот способ более многословен, чем просто вызов Invoke / Begin ?!
MeTitus 28.09.2015 12:36:41
Task.Delay(500).Wait()? Какой смысл создавать Задачу, чтобы просто заблокировать текущий поток? Вы никогда не должны блокировать поток пула потоков!
Yarik 15.12.2016 19:22:55

Вы должны использовать invoke и делегировать

private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
12
10.09.2013 13:55:34

Я предпочитаю это:

private void UpdateNowProcessing(string nowProcessing)
{
    if (this.InvokeRequired)
    {
        Action<string> d = UpdateNowProcessing;
        Invoke(d, nowProcessing);
    }
    else
    {
        this.progressDialog.Next(nowProcessing);
    }            
}
3
26.01.2017 08:21:13

Я хотел добавить предупреждение, потому что заметил, что некоторые простые решения пропускают InvokeRequiredпроверку.

Я заметил, что если ваш код выполняется до того, как дескриптор окна элемента управления был создан (например, до отображения формы), Invokeвыдается исключение. Поэтому я рекомендую всегда проверять InvokeRequiredперед звонком Invokeили BeginInvoke.

6
6.02.2014 09:46:30

Самый простой способ, я думаю:

   void Update()
   {
       BeginInvoke((Action)delegate()
       {
           //do your update
       });
   }
8
18.02.2014 04:41:41

Создайте переменную класса:

SynchronizationContext _context;

Установите его в конструкторе, который создает ваш пользовательский интерфейс:

var _context = SynchronizationContext.Current;

Когда вы хотите обновить ярлык:

_context.Send(status =>{
    // UPDATE LABEL
}, null);
13
26.03.2017 10:10:52

Например, получить доступ к элементу управления, кроме текущего потока:

Speed_Threshold = 30;
textOutput.Invoke(new EventHandler(delegate
{
    lblThreshold.Text = Speed_Threshold.ToString();
}));

Это lblThresholdметка и Speed_Thresholdглобальная переменная.

8
26.03.2017 10:11:42

Может быть, немного передозировки, но это способ, которым я обычно решаю это:

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

Это просто, потому что вам не нужно обрабатывать вещи в потоке пользовательского интерфейса!

public partial class Form1 : Form
{
    BasicClassThreadExample _example;

    public Form1()
    {
        InitializeComponent();
        _example = new BasicClassThreadExample();
        _example.MessageReceivedEvent += _example_MessageReceivedEvent;
    }

    void _example_MessageReceivedEvent(string command)
    {
        listBox1.Items.Add(command);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        listBox1.Items.Clear();
        _example.Start();
    }
}

public class BasicClassThreadExample : IDisposable
{
    public delegate void MessageReceivedHandler(string msg);

    public event MessageReceivedHandler MessageReceivedEvent;

    protected virtual void OnMessageReceivedEvent(string msg)
    {
        MessageReceivedHandler handler = MessageReceivedEvent;
        if (handler != null)
        {
            handler(msg);
        }
    }

    private System.Threading.SynchronizationContext _SynchronizationContext;
    private System.Threading.Thread _doWorkThread;
    private bool disposed = false;

    public BasicClassThreadExample()
    {
        _SynchronizationContext = System.ComponentModel.AsyncOperationManager.SynchronizationContext;
    }

    public void Start()
    {
        _doWorkThread = _doWorkThread ?? new System.Threading.Thread(dowork);

        if (!(_doWorkThread.IsAlive))
        {
            _doWorkThread = new System.Threading.Thread(dowork);
            _doWorkThread.IsBackground = true;
            _doWorkThread.Start();
        }
    }

    public void dowork()
    {
        string[] retval = System.IO.Directory.GetFiles(@"C:\Windows\System32", "*.*", System.IO.SearchOption.TopDirectoryOnly);
        foreach (var item in retval)
        {
            System.Threading.Thread.Sleep(25);
            _SynchronizationContext.Post(new System.Threading.SendOrPostCallback(delegate(object obj)
            {
                OnMessageReceivedEvent(item);
            }), null);
        }
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                _doWorkThread.Abort();
            }
            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~BasicClassThreadExample() { Dispose(false); }

}
5
26.03.2017 10:14:03

Когда вы находитесь в потоке пользовательского интерфейса, вы можете запросить у него планировщик задач контекста синхронизации. Это даст вам 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;
  }
}

Это работает для задач (не потоков), которые сейчас являются предпочтительным способом написания параллельного кода .

8
26.03.2017 10:15:32
Вызов , Task.Startкак правило , не является хорошей практикой blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspx
Ohad Schneider 24.05.2014 08:07:12

Подавляющее большинство ответов используют условия гонки, ожидающие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при его создании), как продемонстрировал Рышард Деган (Ryszard Dżegan), для случаев, когда при длительной операции необходимо запускать код пользовательского интерфейса во время работы.

41
23.05.2017 12:34:51