Как мне обновить графический интерфейс из другого потока? - PullRequest
1279 голосов
/ 19 марта 2009

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

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

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

Ответы [ 47 ]

7 голосов
/ 18 февраля 2014

Самый простой способ, которым я считаю:

   void Update()
   {
       BeginInvoke((Action)delegate()
       {
           //do your update
       });
   }
6 голосов
/ 06 февраля 2014

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

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

6 голосов
/ 19 декабря 2015

Я не мог понять логику 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;
}

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

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 голосов
/ 31 октября 2015

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

Еще один пример по теме: я создал абстрактный класс 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 голосов
/ 01 мая 2014

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

Вызовы здесь не требуются из-за синхронизации. 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 голосов
/ 16 мая 2016

Вот новый взгляд на давнюю проблему с использованием более функционального стиля. Если вы сохраняете класс 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;
    }
}
4 голосов
/ 31 января 2015

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

Пример:

SynchronizationContext ctx = SynchronizationContext.Current; // From control
ctx.Send\Post... // From worker thread
3 голосов
/ 18 ноября 2018

И еще один общий Контроль расширение доступа ..

Сначала добавьте метод расширения для объектов типа 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 голосов
/ 25 марта 2017

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

Пример:

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";
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...