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

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

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

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

1521
CruelIO 19 Мар 2009 в 12:37

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

ОБНОВЛЕНИЕ 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.

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

798
Ian Chu Te 3 Мар 2015 в 00:52

Это в моем варианте решения Яна Кемпа на 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
Rotaerk 15 Сен 2011 в 20:40

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

21
A876 24 Апр 2017 в 07:23

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

 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });
18
Hassan Shouman 11 Янв 2016 в 19: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}, вы просто знаете, что "эту функцию безопасно вызывать из другого потока".

15
Peter Mortensen 26 Мар 2017 в 10:00

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

private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}
15
Peter Mortensen 26 Мар 2017 в 10:01

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

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

SynchronizationContext _context;

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

var _context = SynchronizationContext.Current;

Если вы хотите обновить этикетку:

_context.Send(status =>{
    // UPDATE LABEL
}, null);
14
Peter Mortensen 26 Мар 2017 в 10:10

Попробуйте обновить этикетку с помощью этого

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

    public static void Refresh(this UIElement uiElement)
    {
        uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }
}
13
Ivaylo Slavov 17 Апр 2014 в 09:56

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

1146
Peter Mortensen 16 Сен 2017 в 09:20

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

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

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

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

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

  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. Task вместо Тема.
  3. async, позволяющее использовать ключевое слово await, что, в свою очередь, не позволяет обработчику события достичь состояния завершения до тех пор, пока задача не будет завершена и тем временем не блокирует поток пользовательского интерфейса.
  4. Класс Progress (см. интерфейс IProgress), который поддерживает Разделение проблем (SoC) и не требует явного диспетчера и вызова. Он использует текущий SynchronizationContext из места его создания (здесь поток пользовательского интерфейса).
  5. TaskCreationOptions.LongRunning, намекающий не стоять в очереди задачу в ThreadPool.

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

См. Также о концепции модели потоковой передачи пользовательского интерфейса.

Обработка исключений

Приведенный ниже фрагмент является примером того, как обрабатывать исключения и переключать свойство кнопки 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...");
    }
}
428
Community 23 Май 2017 в 11:55

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

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

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

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

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

259
Community 23 Май 2017 в 12:34

Метод расширения "выстрелил и забыл" для .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");
144
StyxRiver 27 Авг 2010 в 21:10

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

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

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

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

69
Peter Mortensen 26 Мар 2017 в 09:45

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

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

void updateGUI() {
    // update gui here
}
65
MBH 19 Дек 2015 в 10:02

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

50
Don Kirkby 22 Сен 2012 в 03:59

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

44
Community 23 Май 2017 в 12:34

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

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

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

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

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, который отвлечет вас от этого вопроса.

40
Frederik Gheysels 16 Сен 2017 в 10:05

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

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, который их тесно связывает.
  • Поток пользовательского интерфейса не будет препятствовать работе рабочего потока.
  • Рабочий поток не может доминировать над временем, которое поток пользовательского интерфейса тратит на обновление.
  • Интервалы, с которыми пользовательский интерфейс и рабочие потоки выполняют операции, могут оставаться независимыми.
  • Рабочий поток не может переполнить насос сообщений потока пользовательского интерфейса.
  • Поток пользовательского интерфейса определяет, когда и как часто обновляется пользовательский интерфейс.
33
5 revs, 2 users 93% 26 Мар 2017 в 09:48

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

Например:

delegate void UpdateLabelDelegate (string message);

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

    MyLabelControl.Text = message;
}
31
Kieron 19 Мар 2009 в 09:47

Ни один из элементов 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
}
29
Peter Mortensen 26 Мар 2017 в 10:06

Это решение аналогично описанному выше с использованием .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");
25
Peter Mortensen 26 Мар 2017 в 09:56

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

У меня есть диалоговая форма под названием form_Diagnostics,, в которой есть поле с расширенным текстом под названием 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
25
Peter Mortensen 26 Мар 2017 в 09:58
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().

23
Tshilidzi Mudau 8 Мар 2017 в 12:44

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

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

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

22
Frankg 26 Авг 2010 в 03:17

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

private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
12
A. Zalonis 10 Сен 2013 в 13:55

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

this.Dispatcher.Invoke((Action)(() =>
{
    // This refers to a form in a WPF application 
    val1 = textBox.Text; // Access the UI 
}));
10
Peter Mortensen 26 Мар 2017 в 10:26

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

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

Здесь lblThreshold - это метка, а Speed_Threshold - глобальная переменная.

9
Peter Mortensen 26 Мар 2017 в 10:11

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

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

9
Peter Mortensen 26 Мар 2017 в 10:15

И еще один общий подход к расширению 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);
    }
}

И вызовите, как это, из другого потока, чтобы получить доступ к Control с именем object1 в UI-потоке:

object1.InvokeIfRequired(c => { c.Visible = true; });
object1.InvokeIfRequired(c => { c.Text = "ABC"; });

..или вот так

object1.InvokeIfRequired(c => 
  { 
      c.Text = "ABC";
      c.Visible = true; 
  }
);
9
flodis 17 Ноя 2018 в 21:40