Как мне обновить графический интерфейс из другого потока?

Если вы используете Roboguice, вы можете использовать EventManager в Roboguice для передачи данных без использования Activity в качестве интерфейса. Это довольно чистый ИМО.

Если вы не используете Roboguice, вы можете использовать Otto также как шину событий: http://square.github.com/otto/

Обновление 20150909: вы также можете использовать Green Bus Robot Event Bus или даже RxJava. Зависит от вашего прецедента.

1287
задан Uwe Keim 18 February 2018 в 12:05
поделиться

10 ответов

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

ОБНОВЛЕНИЕ 10.05.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 версии.

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

752
ответ дан Ian Chu Te 18 February 2018 в 22:05
поделиться

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

1037
ответ дан Peter Mortensen 18 February 2018 в 22:05
поделиться

Простое решение состоит в том, чтобы использовать Control.Invoke.

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

void updateGUI() {
    // update gui here
}
61
ответ дан MBH 18 February 2018 в 22:05
поделиться

Необходимо будет удостовериться, что обновление происходит на корректном потоке; поток UI.

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

можно сделать это путем генерирования события как это:

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

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

35
ответ дан Frederik Gheysels 18 February 2018 в 22:05
поделиться
  • 1
    Я вернулся по этому вопросу теперь, поскольку у меня есть еще некоторое время на моих руках; я полностью согласен с Вашими точками объяснение 1-4, почему я испытываю затруднения при определении местоположения работающий код. Я был потрясен быть честным хотя, потому что я полагал, что буду в состоянии найти некоторых просто в академических целях, там isn' t очень Вы can' t находят в Интернете в эти дни (проекты бомбы, сложные алгоритмы шифрования, перепроектируя инструменты, и т.д.), таким образом, я действительно нахожу его немного нечетным. Однако, хороший аргумент. Я понимаю, почему скомпилированный язык как C, вероятно, требуется для истинного метаморфизма, но Вашим примером PHP является хороший! – jwbensley 5 May 2013 в 02:29

Это - классический способ, которым необходимо сделать это:

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

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

Тогда в UI необходимо пересечь потоки для изменения фактического управления... как маркировка или индикатор выполнения.

65
ответ дан Peter Mortensen 18 February 2018 в 22:05
поделиться
  • 1
    Спасибо. Я действительно любил работать над этим. Это был большой вопрос. – James Holderness 11 May 2013 в 21:11

Необходимо будет Вызвать метод на поток GUI. Можно сделать это путем вызова Управления. Вызвать.

, Например:

delegate void UpdateLabelDelegate (string message);

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

    MyLabelControl.Text = message;
}
29
ответ дан Kieron 18 February 2018 в 22:05
поделиться
  • 1
    Это для редактора. I' m почти бесспорный вопрос был действительно об окне вывода. – coge.soft 16 December 2012 в 08:37

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

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

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

21
ответ дан 19 December 2019 в 20:15
поделиться

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

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
ответ дан 19 December 2019 в 20:15
поделиться

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

46
ответ дан 19 December 2019 в 20:15
поделиться

Метод расширения "запустил и забыл" для .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");
135
ответ дан 19 December 2019 в 20:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: