it-swarm.com.ru

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

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

У меня есть Form для thread1, и с этого я запускаю другой поток (thread2). Пока thread2 обрабатывает некоторые файлы, я хотел бы обновить Label для Form с текущим статусом работы thread2.

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

1280
CruelIO

Для .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 ||
      [email protected]().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.

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

743
Ian Kemp

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

1013
Marc Gravell

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

Поскольку .NET 4.5 и C # 5. вы должны использовать Асинхронный шаблон на основе задач (TAP) наряду с асинхронным - await ключевые слова во всех областях (включая графический интерфейс ):

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 #: хорошие вещи приходят к тем, кто "ждет" by Джозеф Албахари .

Смотрите также о концепции I 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...");
    }
}
374
Ryszard Dżegan

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

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

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

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

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

220
Zaid Masud

Запустите и забудьте метод расширения для .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");
131
StyxRiver

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

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

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

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

64
Hath

Простое решение - использовать Control.Invoke.

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

void updateGUI() {
    // update gui here
}
59
OregonGhost

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

45
Don Kirkby

В подавляющем большинстве ответов используется 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's ответ для эквивалентного подхода на основе задач (используя TaskScheduler.FromCurrentSynchronizationContext).

Наконец, в .NET 4.5 и более поздних версиях вы также можете использовать Progress<T> (который в основном захватывает SynchronizationContext.Current при его создании), как продемонстрировано Ryszard Dżegan's для случаев, когда длительная операция должна запускать код пользовательского интерфейса, все еще работая ,.

39
Ohad Schneider

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

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

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

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

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

34
Frederik Gheysels

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

Например:

delegate void UpdateLabelDelegate (string message);

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

    MyLabelControl.Text = message;
}
28
Kieron

В предыдущих ответах не требуется ничего из 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
}
26
Jon H

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

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, который тесно связывает их.
  • Поток пользовательского интерфейса не будет препятствовать продвижению рабочего потока.
  • Рабочий поток не может доминировать во время, которое поток пользовательского интерфейса тратит на обновление.
  • Интервалы, с которыми пользовательский интерфейс и рабочие потоки выполняют операции, могут оставаться независимыми.
  • Рабочий поток не может переполнить насос сообщений потока пользовательского интерфейса.
  • Поток пользовательского интерфейса определяет, когда и как часто пользовательский интерфейс обновляется.
26
Brian Gideon

Это похоже на решение выше с использованием .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");
22
Francis

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
22
bgmCoder

Это в моем 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. Это улучшает статическую безопасность типов.

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

20
Rotaerk

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

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

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

20
Frankg

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

19
ahmar
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().

19
ILoveFortran

Просто используйте что-то вроде этого:

 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });
16
Hassan Shouman

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

private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}
15
Embedd_Khurja

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

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

    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
MajesticRa

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

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

    public static void Refresh(this UIElement uiElement)
    {
        uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }
}
13
user1360355

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

SynchronizationContext _context;

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

var _context = SynchronizationContext.Current;

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

_context.Send(status =>{
    // UPDATE LABEL
}, null);
13
blackmind

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

private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
12
A. Zalonis

Большинство других ответов немного сложны для меня по этому вопросу (я новичок в 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";
});
9
Manohar Reddy Poreddy

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

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

Там lblThreshold является меткой, а Speed_Threshold является глобальной переменной.

8
Da Xiong

Я только что прочитал ответы, и это, кажется, очень горячая тема. В настоящее время я использую .NET 3.5 SP1 и Windows Forms.

Хорошо известная формула, в значительной степени описанная в предыдущих ответах, которая использует свойство InvokeRequired, охватывает большинство случаев, но не весь пул.

Что если Handle еще не создано?

Свойство InvokeRequired, как описано здесь (ссылка на свойство Control.InvokeRequired для MSDN) возвращает значение true, если вызов был сделан из потока, не являющегося потоком графического интерфейса пользователя, либо значение false, если вызов был сделан из потока GUI, или если Handle еще не было создано.

Вы можете встретить исключение, если вы хотите, чтобы модальная форма отображалась и обновлялась другим потоком. Поскольку вы хотите, чтобы эта форма отображалась модально, вы можете сделать следующее:

private MyForm _gui;

public void StartToDoThings()
{
    _gui = new MyForm();
    Thread thread = new Thread(SomeDelegate);
    thread.Start();
    _gui.ShowDialog();
}

И делегат может обновить метку в графическом интерфейсе:

private void SomeDelegate()
{
    // Operations that can take a variable amount of time, even no time
    //... then you update the GUI
    if(_gui.InvokeRequired)
        _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
    else
        _gui.Label1.Text = "Done!";
}

Это может вызвать InvalidOperationException, если операции перед обновлением метки "занимают меньше времени" (считайте его и интерпретируйте как упрощение), чем время, необходимое потоку GUI для создания [ Form 's Handle. Это происходит в методе ShowDialog ().

Вы также должны проверить Handle вот так:

private void SomeDelegate()
{
    // Operations that can take a variable amount of time, even no time
    //... then you update the GUI
    if(_gui.IsHandleCreated)  //  <---- ADDED
        if(_gui.InvokeRequired)
            _gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
        else
            _gui.Label1.Text = "Done!";
}

Вы можете обработать выполняемую операцию, если Handle еще не создано: вы можете просто проигнорировать обновление GUI (как показано в коде выше) или подождать (более рискованно). Это должно ответить на вопрос.

Необязательный материал: лично я придумал кодировать следующее:

public class ThreadSafeGuiCommand
{
  private const int SLEEPING_STEP = 100;
  private readonly int _totalTimeout;
  private int _timeout;

  public ThreadSafeGuiCommand(int totalTimeout)
  {
    _totalTimeout = totalTimeout;
  }

  public void Execute(Form form, Action guiCommand)
  {
    _timeout = _totalTimeout;
    while (!form.IsHandleCreated)
    {
      if (_timeout <= 0) return;

      Thread.Sleep(SLEEPING_STEP);
      _timeout -= SLEEPING_STEP;
    }

    if (form.InvokeRequired)
      form.Invoke(guiCommand);
    else
      guiCommand();
  }
}

Я передаю свои формы, которые обновляются другим потоком, экземпляром этого ThreadSafeGuiCommand, и я определяю методы, которые обновляют графический интерфейс (в моей форме) следующим образом:

public void SetLabeTextTo(string value)
{
  _threadSafeGuiCommand.Execute(this, delegate { Label1.Text = value; });
}

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

8
Sume

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

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

   void Update()
   {
       BeginInvoke((Action)delegate()
       {
           //do your update
       });
   }
7
Vasily Semenov

Самый простой способ в приложениях WPF:

this.Dispatcher.Invoke((Action)(() =>
{
    // This refers to a form in a WPF application 
    val1 = textBox.Text; // Access the UI 
}));
7
Basheer AL-MOMANI

Даже если операция занимает много времени (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; 
     });
 }
6
Yuliia Ashomok

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

void setEnableLoginButton()
{
  if (InvokeRequired)
  {
    // btn_login can be any conroller, (label, button textbox ..etc.)

    btn_login.Invoke(new MethodInvoker(setEnable));

    // OR
    //Invoke(new MethodInvoker(setEnable));
  }
  else {
    setEnable();
  }
}

void setEnable()
{
  btn_login.Enabled = isLoginBtnEnabled;
}

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

Task.Factory.StartNew(()=>
{
    // THIS IS NOT GUI
    Thread.Sleep(5000);
    // HERE IS INVOKING GUI
    btn_login.Invoke(new Action(() => DoSomethingOnGUI()));
});

private void DoSomethingOnGUI()
{
   // GUI
   MessageBox.Show("message", "title", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}

Еще проще:

btn_login.Invoke(new Action(()=>{ /* HERE YOU ARE ON GUI */ }));
6
MBH

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

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

6
Jos Bosmans

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

public class Example
{
    /// <summary>
    /// No more delegates, background workers, etc. Just one line of code as shown below.
    /// Note it is dependent on the Task Extension method shown next.
    /// </summary>
    public async void Method1()
    {
        // Still on the GUI thread here if the method was called from the GUI thread
        // This code below calls the extension method which spins up a new task and calls back.
        await TaskXM.RunCodeAsync(() =>
        {
            // Running an asynchronous task here
            // Cannot update the GUI thread here, but can do lots of work
        });
        // Can update GUI on this line
    }
}


/// <summary>
/// A class containing extension methods for the Task class
/// </summary>
public static class TaskXM
{
    /// <summary>
    /// RunCodeAsyc is an extension method that encapsulates the Task.run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunCodeAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}
5
John Peters

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

Вызовы здесь не требуются из-за синхронизации. 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
Carsten R.

Еще один пример на эту тему: я создал абстрактный класс UiSynchronizeModel, который содержит реализацию общего метода:

public abstract class UiSynchronizeModel
{
    private readonly TaskScheduler uiSyncContext;
    private readonly SynchronizationContext winformsOrDefaultContext;

    protected UiSynchronizeModel()
    {
        this.winformsOrDefaultContext = SynchronizationContext.Current ?? new SynchronizationContext();
        this.uiSyncContext = TaskScheduler.FromCurrentSynchronizationContext();
    }

    protected void RunOnGuiThread(Action action)
    {
        this.winformsOrDefaultContext.Post(o => action(), null);
    }

    protected void CompleteTask(Task task, TaskContinuationOptions options, Action<Task> action)
    {
        task.ContinueWith(delegate
        {
            action(task);
            task.Dispose();
        }, CancellationToken.None, options, this.uiSyncContext);
    }
}

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

public void MethodThatCalledFromBackroundThread()
{
   this.RunOnGuiThread(() => {
       // Do something over UI controls
   });
}

Пример задач:

var task = Task.Factory.StartNew(delegate
{
    // Background code
    this.RunOnGuiThread(() => {
        // Do something over UI controls
    });
});

this.CompleteTask(task, TaskContinuationOptions.OnlyOnRanToCompletion, delegate
{
    // Code that can safely use UI controls
});
5
Alexander Egorov

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

Пример:

SynchronizationContext ctx = SynchronizationContext.Current; // From control
ctx.Send\Post... // From worker thread
4
Roman Ambinder

И еще одно общее расширение 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; 
  }
);
3
flodis

Сначала получите экземпляр вашей формы (в данном случае mainForm), а затем просто используйте этот код в другом потоке.

mainForm.Invoke(new MethodInvoker(delegate () 
{
    // Update things in my mainForm here
    mainForm.UpdateView();
}));
3
Musculaa

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

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

Поместите некоторую общую переменную в отдельный класс для хранения значения.

Пример:

public  class data_holder_for_controls
{
    // It will hold the value for your label
    public string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();

    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread
    }

    public static void perform_logic()
    {
        // Put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            // Statements here
        }
        // Set the result in the status variable
        d1.status = "Task done";
    }
}
3
Saurabh

просто используйте контекст синхронизации пользовательского интерфейса

using System.Threading;

// ...

public partial class MyForm : Form
{
    private readonly SynchronizationContext uiContext;

    public MyForm()
    {
        InitializeComponent();
        uiContext = SynchronizationContext.Current; // get ui thread context
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Thread t = new Thread(() =>
            {// set ui thread context to new thread context                            
             // for operations with ui elements to be performed in proper thread
             SynchronizationContext
                 .SetSynchronizationContext(uiContext);
             label1.Text = "some text";
            });
        t.Start();
    }
}
2
user53373

В моем случае (WPF) решение простое:

private void updateUI()
{
    if (!Dispatcher.CheckAccess())
    {
        Dispatcher.BeginInvoke(updateUI);
        return;
    }

    // Update any number of controls here
}
2
ipe

Общий подход такой:

using System;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        int clickCount = 0;

        public Form1()
        {
            InitializeComponent();
            label1.SetText("0");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            new Thread(() => label1.SetText((++clickCount).ToString())).Start();
        }
    }

    public static class ControlExtensions
    {
        public static void SetText(this Control control, string text)
        {
            if (control.InvokeRequired)
                control.Invoke(setText, control, text);
            else
                control.Text = text;
        }

        private static readonly Action<Control, string> setText =
            (control, text) => control.Text = text;
    }
}

Объяснение :

Ответ очень похож на этот . Но использует более аккуратный (как для меня) и более новый синтаксис. Дело в том, что InvokeRequired свойство control. Он получает значение, указывающее, должен ли вызывающий объект вызывать метод invoke при вызове метода для элемента управления, поскольку вызывающий объект находится в потоке, отличном от того, в котором был создан элемент управления. Поэтому, если мы вызываем control.SetText("some text") в том же потоке, в котором был создан control, то можно просто установить Text в качестве этого control.Text = text. Но в любом другом потоке это вызывает System.InvalidOperationException, поэтому нужно вызвать метод через control.Invoke(...), чтобы установить Text в потоке, в котором было создано control.

1
Alex

Простейший способ вызывается следующим образом:

 Application.Current.Dispatcher.Invoke(new Action(() =>
             {
                    try
                    {
                        ///
                    }
                    catch (Exception)
                    {
                      //
                    }


                    }
     ));
0
Lankan