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

каков самый простой способ обновления a Label из другого потока?

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

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

30 ответов


для .NET 2.0, вот хороший бит кода, который я написал, который делает именно то, что вы хотите, и работает для любого свойства на Control:

private delegate void SetControlPropertyThreadSafeDelegate(
    Control control, 
    string propertyName, 
    object propertyValue);

public static void SetControlPropertyThreadSafe(
    Control control, 
    string propertyName, 
    object propertyValue)
{
  if (control.InvokeRequired)
  {
    control.Invoke(new SetControlPropertyThreadSafeDelegate               
    (SetControlPropertyThreadSafe), 
    new object[] { control, propertyName, propertyValue });
  }
  else
  {
    control.GetType().InvokeMember(
        propertyName, 
        BindingFlags.SetProperty, 
        null, 
        control, 
        new object[] { propertyValue });
  }
}

назовем это так:

// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);

если вы используете .NET 3.0 или выше, вы можете переписать вышеуказанный метод как метод расширения Control класс, который затем упростит вызов:

myLabel.SetPropertyThreadSafe("Text", status);

05/10/2010 обновления:

для .NET 3.0 вы должны использовать это код:

private delegate void SetPropertyThreadSafeDelegate<TResult>(
    Control @this, 
    Expression<Func<TResult>> property, 
    TResult value);

public static void SetPropertyThreadSafe<TResult>(
    this Control @this, 
    Expression<Func<TResult>> property, 
    TResult value)
{
  var propertyInfo = (property.Body as MemberExpression).Member 
      as PropertyInfo;

  if (propertyInfo == null ||
      !@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
      @this.GetType().GetProperty(
          propertyInfo.Name, 
          propertyInfo.PropertyType) == null)
  {
    throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
  }

  if (@this.InvokeRequired)
  {
      @this.Invoke(new SetPropertyThreadSafeDelegate<TResult> 
      (SetPropertyThreadSafe), 
      new object[] { @this, property, value });
  }
  else
  {
      @this.GetType().InvokeMember(
          propertyInfo.Name, 
          BindingFlags.SetProperty, 
          null, 
          @this, 
          new object[] { value });
  }
}

который использует выражения LINQ и lambda, чтобы обеспечить более чистый, простой и безопасный синтаксис:

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.

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


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


работа долгая работа

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

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. реализация второго потока, уведомляет UI-потоке:

    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. асинхронные ключевое слово, которое позволяет использовать ждут который в свою очередь предотвращает обработчик событий от достижения состояния завершения до завершения задачи и в тем временем не блокирует поток пользовательского интерфейса.
  4. класс прогресс (см. Интерфейс IProgress), который поддерживает разделение забот (SoC) принцип проектирования и не требует явного диспетчера и вызова. Он использует текущий SynchronizationContext С места его создания (здесь поток пользовательского интерфейса).
  5. TaskCreationOptions.Затяжные что намекает на не очереди задач в ThreadPool.

для более подробных примеров см.:будущее C#: хорошие вещи приходят к тем, кто "ждет" by Джозеф Albahari.

Читайте также о UI Threading Model


вариация Марк Gravell-это простой решение для .NET 4:

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

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

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

см. здесь для сравнения двух: MethodInvoker против действия для управления.Метод BeginInvoke


огонь и забыть метод расширения для .NET 3.5+

using System;
using System.Windows.Forms;

public static class ControlExtensions
{
    /// <summary>
    /// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread.
    /// </summary>
    /// <param name="control"></param>
    /// <param name="code"></param>
    public static void UIThread(this Control @this, Action code)
    {
        if (@this.InvokeRequired)
        {
            @this.BeginInvoke(code);
        }
        else
        {
            code.Invoke();
        }
    }
}

Это можно вызвать, используя следующую строку кода:

this.UIThread(() => this.myLabel.Text = "Text Goes Here");

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

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

namespace Test
{
    public partial class UIThread : Form
    {
        Worker worker;

        Thread workerThread;

        public UIThread()
        {
            InitializeComponent();

            worker = new Worker();
            worker.ProgressChanged += new EventHandler<ProgressChangedArgs>(OnWorkerProgressChanged);
            workerThread = new Thread(new ThreadStart(worker.StartWork));
            workerThread.Start();
        }

        private void OnWorkerProgressChanged(object sender, ProgressChangedArgs e)
        {
            // Cross thread - so you don't get the cross-threading exception
            if (this.InvokeRequired)
            {
                this.BeginInvoke((MethodInvoker)delegate
                {
                    OnWorkerProgressChanged(sender, e);
                });
                return;
            }

            // Change control
            this.label1.Text = e.Progress;
        }
    }

    public class Worker
    {
        public event EventHandler<ProgressChangedArgs> ProgressChanged;

        protected void OnProgressChanged(ProgressChangedArgs e)
        {
            if(ProgressChanged!=null)
            {
                ProgressChanged(this,e);
            }
        }

        public void StartWork()
        {
            Thread.Sleep(100);
            OnProgressChanged(new ProgressChangedArgs("Progress Changed"));
            Thread.Sleep(100);
        }
    }


    public class ProgressChangedArgs : EventArgs
    {
        public string Progress {get;private set;}
        public ProgressChangedArgs(string progress)
        {
            Progress = progress;
        }
    }
}

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

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


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

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

void updateGUI() {
    // update gui here
}

Threading код часто багги и всегда трудно проверить. Вам не нужно писать потоковый код для обновления пользовательского интерфейса из фоновой задачи. Просто используйте BackgroundWorker класс для запуска задачи и ее ReportProgress метод обновления пользовательского интерфейса. Обычно вы просто сообщаете процент завершения, но есть другая перегрузка, которая включает объект состояния. Вот пример, который просто сообщает объект string:

    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 метод будет полностью проигнорирована.


подавляющее большинство ответов использовать Control.Invoke что это состязания ждет. Например, рассмотрим принятый ответ:

string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate { 
    someLabel.Text = newText; // runs on UI thread
});

если пользователь закрывает форму непосредственно перед this.Invoke называется (помню, this - это Form объект), an ObjectDisposedException скорее всего будет уволен.

решение заключается в использовании SynchronizationContext, в частности SynchronizationContext.Current as Гамильтон.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 и выше вы действительно должны использовать задачи для асинхронных операций. См.Н-Сан ответ для эквивалентного подхода на основе задач (используя TaskScheduler.FromCurrentSynchronizationContext).

наконец, на .NET 4.5 и выше вы также можете использовать Progress<T> (который в основном захватывает SynchronizationContext.Current при его создании), как показано Рышард Dżegan это для случаев, когда длительная операция должна запускать 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, который будет аннотация этот вопрос для вас.


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

например:

delegate void UpdateLabelDelegate (string message);

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

    MyLabelControl.Text = message;
}

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

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

ни один из материалов 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
}

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

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

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


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

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


этот похож на решение выше, используя .NET Framework 3.0, но он решил проблему поддержка безопасности во время компиляции.

public  static class ControlExtension
{
    delegate void SetPropertyValueHandler<TResult>(Control souce, Expression<Func<Control, TResult>> selector, TResult value);

    public static void SetPropertyValue<TResult>(this Control source, Expression<Func<Control, TResult>> selector, TResult value)
    {
        if (source.InvokeRequired)
        {
            var del = new SetPropertyValueHandler<TResult>(SetPropertyValue);
            source.Invoke(del, new object[]{ source, selector, value});
        }
        else
        {
            var propInfo = ((MemberExpression)selector.Body).Member as PropertyInfo;
            propInfo.SetValue(source, value, null);
        }
    }
}

использование:

this.lblTimeDisplay.SetPropertyValue(a => a.Text, "some string");
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, false);

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

this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");

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

у меня есть диалоговая форма под названием form_Diagnostics, который имеет окно richtext, называемое updateDiagWindow, который я использую в качестве своего рода отображения журнала. Мне нужно было обновить его текст из всех потоков. Дополнительная строка разрешить автоматическую прокрутку окна до последних строк.

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

  form_Diagnostics.updateDiagWindow(whatmessage);

основной код (поместите это внутри кода класса вашей формы):

#region "---------Update Diag Window Text------------------------------------"
// This sub allows the diag window to be updated by all threads
public void updateDiagWindow(string whatmessage)
{
    var _with1 = diagwindow;
    if (_with1.InvokeRequired) {
        _with1.Invoke(new UpdateDiagDelegate(UpdateDiag), whatmessage);
    } else {
        UpdateDiag(whatmessage);
    }
}
// This next line makes the private UpdateDiagWindow available to all threads
private delegate void UpdateDiagDelegate(string whatmessage);
private void UpdateDiag(string whatmessage)
{
    var _with2 = diagwindow;
    _with2.appendtext(whatmessage);
    _with2.SelectionStart = _with2.Text.Length;
    _with2.ScrollToCaret();
}
#endregion

Label lblText; //initialized elsewhere

void AssignLabel(string text)
{
   if (InvokeRequired)
   {
      BeginInvoke((Action<string>)AssignLabel, text);
      return;
   }

   lblText.Text = text;           
}

отметим, что BeginInvoke() предпочтительнее, чем Invoke() потому что это менее вероятно, чтобы вызвать взаимоблокировки (однако, это не проблема здесь, когда просто присвоение текста метке):

при использовании Invoke() вы ждете возвращения метода. Теперь может быть, что вы делаете что-то в вызываемом коде, который должен будет ждать потока, что может быть не сразу очевидно, если он похоронен в некоторых вызываемых вами функциях, что само по себе может произойти косвенно через событие дрессировщики. Таким образом, вы будете ждать нити, нить будет ждать вас, и вы находитесь в тупике.

это на самом деле вызвало некоторые из наших программ, чтобы повесить. Это было достаточно легко исправить, заменив Invoke() С BeginInvoke(). Если у вас нет необходимости в синхронной работе, что может произойти, если вам нужно возвращаемое значение, используйте BeginInvoke().


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

сделайте делегата следующим образом:

Public delegate void LabelDelegate(string s);

void Updatelabel(string text)
{
   if (label.InvokeRequired)
   {
       LabelDelegate LDEL = new LabelDelegate(Updatelabel);
       label.Invoke(LDEL, text);
   }
   else
       label.Text = text
}

вы можете вызвать эту функцию в новом потоке, как это

Thread th = new Thread(() => Updatelabel("Hello World"));
th.start();

Не путайте с Thread(() => .....). Я использую анонимную функцию или лямбда-выражение, когда работаю над потоком. Чтобы уменьшить строки кода, Вы можете использовать ThreadStart(..) метод тоже, который я не должен объяснять здесь.


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

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

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

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

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

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

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

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

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

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


вы должны использовать invoke и delegate

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

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

SynchronizationContext _context;

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

var _context = SynchronizationContext.Current;

когда вы хотите обновить надпись:

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

самый простой способ, мне кажется:

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

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

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

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


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

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


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

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

, что если дескриптор еще не создан?

на InvokeRequired собственность, как описано здесь (Контроль.InvokeRequired ссылка на свойство MSDN) возвращает true, если вызов был сделан из потока, который не является потоком GUI, false, если вызов был сделан из потока GUI, или если дескриптор еще не создан.

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

private MyForm _gui;

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

и делегат может обновить метки на GUI:

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 для создания форма ' s дескриптор. Это происходит внутри ShowDialog () метод.

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

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

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

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

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


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

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