Jak zaktualizować GUI z innego wątku?

Jaki jest najprostszy sposób aktualizacji Label z innego wątku?

Mam Form na thread1 i od tego zaczynam kolejny wątek (thread2). Podczas gdy thread2 przetwarza niektóre pliki, chciałbym zaktualizować Label na Form z aktualnym statusem pracy thread2.

Jak mogę to zrobić?

Author: Uwe Keim, 2009-03-19

30 answers

[10]} dla. NET 2.0, oto ładny fragment kodu, który napisałem, że robi dokładnie to, co chcesz, i działa dla każdej właściwości na 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 });
  }
}

Nazwij to tak:

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

Jeśli używasz. NET 3.0 lub nowszego, możesz przepisać powyższą metodę jako metodę rozszerzenia klasy Control, co uprościłoby wywołanie do:

myLabel.SetPropertyThreadSafe("Text", status);

Aktualizacja 05/10/2010:

Dla. NET 3.0 powinieneś użyć tego kodu:

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

Który używa LINQ i lambda wyrażenia pozwalające na znacznie czystszą, prostszą i bezpieczniejszą składnię:

myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile

Nie tylko nazwa właściwości jest teraz sprawdzana podczas kompilacji, ale także jej typ, więc nie jest możliwe (na przykład) przypisanie wartości łańcuchowej do właściwości logicznej, a tym samym wywołanie wyjątku runtime.

Niestety to nie powstrzymuje nikogo przed robieniem głupich rzeczy, takich jak przekazywanie innej własności i wartości Control, więc następujące z radością się skompilują:

myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);

Dlatego dodałem runtime sprawdza, czy właściwość passed-in rzeczywiście należy do Control, w której metoda jest wywoływana. Nie idealnie, ale i tak dużo lepiej niż wersja. Net 2.0.

Jeśli ktoś ma jeszcze jakieś sugestie, jak poprawić ten kod dla bezpieczeństwa kompilacji, proszę o komentarz!

 701
Author: Ian Kemp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-03 00:52:09

Najprostszy sposób jest anonimową metodą przekazywaną do 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

Zauważ, że Invoke blokuje wykonanie dopóki nie zakończy się -- jest to kod synchroniczny. Pytanie nie pyta o kod asynchroniczny, ale na Stack Overflow jest dużo treści o pisaniu kodu asynchronicznego, gdy chcesz się o nim dowiedzieć.

 956
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-16 09:20:29

Obsługa długich prac

Od . NET 4.5 i C# 5.0 Należy używać wzorzec asynchroniczny oparty na zadaniach (TAP) wraz z async-waiting słowa kluczowe we wszystkich obszarach (w tym GUI):

TAP jest zalecanym asynchronicznym wzorcem projektowym dla nowych rozwiązań.]}

Zamiast asynchroniczny model programowania (APM) i asynchroniczny oparty na zdarzeniach Pattern (EAP) (ten ostatni zawiera klasę BackgroundWorker ).

Wtedy, zalecane rozwiązanie dla nowego rozwoju jest:

  1. Nie jest to jednak możliwe, ponieważ nie jest to możliwe.]}
    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. Implementacja drugiego wątku, który powiadamia wątek 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());
            }
        }
    }
    

Zauważ, co następuje:

  1. krótki i czysty kod napisany sekwencyjnie bez wywołań zwrotnych i wyraźnych nici.
  2. Zadanie zamiast wątek.
  3. async słowo kluczowe, które pozwala na użycie wait, które z kolei uniemożliwia obsługę zdarzeń osiągnięcie stanu zakończenia do zakończenia zadania i w międzyczasie nie blokuje wątku interfejsu użytkownika.
  4. klasa Progress (zobacz interfejs IProgress ), która obsługuje zasadę projektowania separacji obaw (SoC) i nie wymaga jawnego dyspozytora i wywoływania. Wykorzystuje prąd SynchronizationContext z miejsca jego utworzenia (tutaj wątek UI).
  5. TaskCreationOptions.LongRunning , który podpowiada, aby nie kolejkować zadania do ThreadPool .
C#: the Future of C#: Good things come to those who 'waiting' byJoseph Albahari .

Zobacz także oUI Threading Model concept.

Obsługa wyjątków

Poniższy fragment jest przykładem tego, jak obsługa wyjątków i Właściwość Enabled przycisku przełączania, aby zapobiec wielokrotnym kliknięciom podczas wykonywania w tle.

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...");
    }
}
 344
Author: Ryszard Dżegan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:02

Wariant najprostszego rozwiązania dla. Net 4:

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

Lub zamiast tego użyj Action delegate:

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

Zobacz tutaj dla porównania dwóch: MethodInvoker vs Action for Control.BeginInvoke

 200
Author: Zaid Masud,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:51

Fire and forget extension method for. 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();
        }
    }
}

Można to wywołać za pomocą następującego wiersza kodu:

this.UIThread(() => this.myLabel.Text = "Text Goes Here");
 117
Author: StyxRiver,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-27 21:10:32

To klasyczny sposób, w jaki powinieneś to zrobić:

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

Twój wątek roboczy ma Zdarzenie. Wątek interfejsu użytkownika uruchamia inny wątek do wykonania pracy i łączy się z tym zdarzeniem roboczym, aby wyświetlić stan wątku roboczego.

Następnie w interfejsie użytkownika musisz krzyżować wątki, aby zmienić rzeczywistą kontrolę... jak etykieta lub pasek postępu.

 59
Author: Hath,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-26 09:45:55

Prostym rozwiązaniem jest użycie Control.Invoke.

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

void updateGUI() {
    // update gui here
}
 52
Author: OregonGhost,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-19 10:02:10

Kod wątkowy jest często wadliwy i zawsze trudny do przetestowania. Nie musisz pisać kodu wątkowego, aby zaktualizować interfejs użytkownika z zadania w tle. Wystarczy użyć klasy BackgroundWorker do uruchomienia zadania i jego metody ReportProgress do aktualizacji interfejsu użytkownika. Zazwyczaj po prostu zgłaszasz procent kompletny, ale jest inne przeciążenie, które obejmuje obiekt stanu. Oto przykład, który zgłasza obiekt 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();
    }

To dobrze, jeśli zawsze chcesz zaktualizuj to samo pole. Jeśli masz do wykonania bardziej skomplikowane aktualizacje, możesz zdefiniować klasę reprezentującą stan interfejsu użytkownika i przekazać ją metodzie ReportProgress.

Jeszcze jedno, upewnij się, że ustawisz flagę WorkerReportsProgress, w przeciwnym razie metoda ReportProgress zostanie całkowicie zignorowana.

 41
Author: Don Kirkby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-22 03:59:50

Zdecydowana większość odpowiedzi używa Control.Invoke, czyli warunku Rasowego czekającego na nadejście . Na przykład rozważmy zaakceptowaną odpowiedź:

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

Jeśli użytkownik zamknie formularz tuż przed wywołaniem this.Invoke (pamiętaj, this jest obiektem Form), prawdopodobnie zostanie wywołany ObjectDisposedException.

Rozwiązaniem jest użycie SynchronizationContext, a konkretnie SynchronizationContext.Current jako Hamiltona.danielb sugeruje (inne odpowiedzi opierają się na konkretnych implementacjach SynchronizationContext, które są całkowicie niepotrzebne). Ja bym w związku z tym, że wątek roboczy zwykle nie musi czekać, należy zmodyfikować jego kod tak, aby używał SynchronizationContext.Post zamiast 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);
    }
}

Zauważ, że na. Net 4.0 i nowszych powinieneś używać zadań do operacji asynchronicznych. Zobacz odpowiedź n-san dla równoważnego podejścia opartego na zadaniach (używając TaskScheduler.FromCurrentSynchronizationContext).

Wreszcie, na. Net 4.5 i nowszych można również użyć Progress<T> (który w zasadzie rejestruje SynchronizationContext.Current po jego utworzeniu), o czym świadczy Ryszarda Dżegana w przypadkach, gdy długotrwała operacja wymaga uruchomienia kodu interfejsu użytkownika podczas pracy.

 32
Author: Ohad Schneider,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:51

Musisz upewnić się, że aktualizacja odbywa się w odpowiednim wątku; wątku interfejsu użytkownika.

Aby to zrobić, musisz wywołać funkcję obsługi zdarzenia zamiast wywoływać ją bezpośrednio.

Możesz to zrobić, podnosząc swoje wydarzenie w ten sposób:

(kod jest wpisywany tutaj z mojej głowy, więc nie sprawdzałem poprawnej składni itp. ale to powinno cię pobudzić.)

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 ( ... );
      }
   }
}

Zauważ, że powyższy kod nie będzie działał na projektach WPF, ponieważ kontrole WPF nie implementują Interfejs ISynchronizeInvoke.

Aby upewnić się, że powyższy kod działa z Windows Forms i WPF oraz wszystkimi innymi platformami, możesz rzucić okiem na AsyncOperation, AsyncOperationManager i SynchronizationContext klasy.

W celu łatwego wywoływania zdarzeń w ten sposób, stworzyłem metodę rozszerzenia, która pozwala mi uprościć wywoływanie zdarzenia poprzez wywołanie:

MyEvent.Raise(this, EventArgs.Empty);

Oczywiście, możesz również skorzystać z klasy BackGroundWorker, która wyjaśni Ci tę sprawę.

 30
Author: Frederik Gheysels,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-16 10:05:49

Będziesz musiał wywołać metodę w wątku GUI. Możesz to zrobić, dzwoniąc do kontroli./ Align = "left" /

Na przykład:

delegate void UpdateLabelDelegate (string message);

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

    MyLabelControl.Text = message;
}
 26
Author: Kieron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-19 09:47:27

Ze względu na trywialność scenariusza chciałbym mieć ankietę UI thread dla statusu. Myślę, że przekonasz się, że może być dość elegancki.

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

Podejście pozwala uniknąć operacji marshalingu wymaganej przy użyciu metod ISynchronizeInvoke.Invoke i ISynchronizeInvoke.BeginInvoke. Nie ma nic złego w stosowaniu techniki marshalingu, ale jest kilka zastrzeżeń, o których musisz pamiętać.

  • upewnij się, że nie dzwonisz BeginInvoke zbyt często, bo może to przekroczyć wiadomość pompa.
  • wywołanie Invoke w wątku roboczym jest wywołaniem blokującym. To tymczasowo zatrzyma pracę wykonywaną w tym wątku.

Strategia, którą proponuję w tej odpowiedzi, odwraca role komunikacyjne wątków. Zamiast wątku roboczego przepychającego dane, wątek interfejsu sprawdza je. Jest to wspólny wzorzec stosowany w wielu scenariuszach. Ponieważ wszystko, co chcesz zrobić, to wyświetlać informacje o postępach z wątku roboczego, myślę, że przekonasz się, że to rozwiązanie jest świetna alternatywa dla rozwiązania marshaling. Ma następujące zalety.

  • interfejs użytkownika i wątki robocze pozostają luźno połączone w przeciwieństwie do podejścia Control.Invoke lub Control.BeginInvoke, które ściśle je łączy.
  • wątek interfejsu użytkownika nie utrudnia postępu wątku roboczego.
  • wątek roboczy nie może zdominować czasu, jaki wątek interfejsu spędza na aktualizacji.
  • interwały, w których wątki UI i worker wykonują operacje, mogą pozostać niezależna.
  • wątek roboczy nie może przekroczyć pompy wiadomości wątku interfejsu użytkownika.
  • wątek interfejsu użytkownika może dyktować, kiedy i jak często interfejs jest aktualizowany.
 23
Author: Brian Gideon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-26 09:48:03

Żadne wywołanie w poprzednich odpowiedziach nie jest konieczne.

Musisz spojrzeć na 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
}
 23
Author: Jon H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-26 10:06:14

Dla wielu celów jest to tak proste:

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

"serviceGUI ()" jest metodą poziomu GUI w formularzu (this), która może zmienić dowolną liczbę kontrolek. Wywołanie "updateGUI()" z drugiego wątku. Parametry mogą być dodawane do wartości pass lub (prawdopodobnie szybciej) używać zmiennych klasy z blokadami, jeśli istnieje jakakolwiek możliwość kolizji między wątkami, które mogłyby spowodować niestabilność. Użyj BeginInvoke zamiast wywoływać, jeśli nie-GUI wątek jest czas krytyczny (mając na uwadze Ostrzeżenie Briana Gideona).

 19
Author: Frankg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-26 03:17:44

To w moim C# 3.0 wariacja rozwiązania Iana Kempa:

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

Nazywasz to tak:

myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
  1. dodaje null-checking do wyniku "as MemberExpression".
  2. Poprawia bezpieczeństwo typu statycznego.

W przeciwnym razie oryginał jest bardzo dobrym rozwiązaniem.

 19
Author: Rotaerk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-15 20:40:57

Ten jest podobny do powyższego przy użyciu. NET Framework 3.0, ale rozwiązał problem wsparcia bezpieczeństwa w czasie kompilacji .

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

Do użycia:

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

Kompilator nie powiedzie się, jeśli użytkownik poda niewłaściwy typ danych.

this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");
 19
Author: Francis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-26 09:56:51

Salvete! Szukając tego pytania, znalazłem odpowiedzi FrankG i Oregon Ghost jako najłatwiejsze najbardziej przydatne dla mnie. Teraz koduję w Visual Basicu i przepuściłem ten fragment przez konwerter, więc nie jestem pewien, jak to się skończy.

Mam formularz dialogowy o nazwie form_Diagnostics,, który ma okno richtext o nazwie updateDiagWindow,, którego używam jako rodzaj wyświetlacza logowania. Musiałem być w stanie zaktualizować jego tekst ze wszystkich wątków. Dodatkowe linie pozwalają na automatycznie przewiń do najnowszych linii.

I tak, mogę teraz zaktualizować wyświetlacz o jedną linię, z dowolnego miejsca w całym programie w sposób, który myślisz, że będzie działał bez żadnego wątku:

  form_Diagnostics.updateDiagWindow(whatmessage);

Kod główny (umieść go w kodzie klasowym formularza):

#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
 19
Author: bgmCoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-26 09:58:47
Label lblText; //initialized elsewhere

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

   lblText.Text = text;           
}

Zauważ, że BeginInvoke() jest preferowane niż Invoke(), ponieważ jest mniej prawdopodobne, że spowoduje blokady (jednak nie jest to problem podczas przypisywania tekstu do etykiety):

Podczas używania Invoke() czekasz na powrót metody. Teraz może być tak, że zrobisz coś w wywołanym kodzie, co będzie musiało poczekać na wątek, co może nie być natychmiast oczywiste, jeśli jest zakopane w niektórych funkcjach, które wywołujesz, co samo w sobie może się zdarzyć pośrednio za pomocą programów obsługi zdarzeń. Więc byś czekać na wątek, wątek będzie czekał na Ciebie i jesteś w martwym punkcie.

To faktycznie spowodowało, że niektóre z naszych wydanych programów zawiesiły się. Łatwo było to naprawić, zastępując Invoke() BeginInvoke(). Jeśli nie masz potrzeby operacji synchronicznej, co może mieć miejsce, jeśli potrzebujesz zwracanej wartości, użyj BeginInvoke().

 18
Author: ILoveFortran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-08 12:44:54

Kiedy natknąłem się na ten sam problem, szukałem pomocy w Google, ale zamiast dać mi proste rozwiązanie, bardziej mnie zdezorientowało, podając przykłady MethodInvoker i bla bla bla. Więc postanowiłem to rozwiązać na własną rękę. Oto moje rozwiązanie:

Stwórz delegata w ten sposób:

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
}

Możesz wywołać tę funkcję w nowym wątku, takim jak ten

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

Nie myl się z Thread(() => .....). Podczas pracy nad wątkiem używam funkcji anonimowej lub wyrażenia lambda. Aby zmniejszyć liczbę linii kodu można też użyć metody ThreadStart(..), której nie powinienem tutaj wyjaśniać.

 16
Author: ahmar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-24 07:23:27

Po prostu użyj czegoś takiego:

 this.Invoke((MethodInvoker)delegate
            {
                progressBar1.Value = e.ProgressPercentage; // runs on UI thread
            });
 14
Author: Hassan Shouman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-11 19:47:30

Możesz użyć już istniejącego delegata Action:

private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}
 13
Author: Embedd_Khurja,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-26 10:01:11

Spróbuj odświeżyć Etykietę używając tego

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

    public static void Refresh(this UIElement uiElement)
    {
        uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
    }
}
 12
Author: Ivaylo Slavov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-17 09:56:54

Moja wersja to wstawienie jednej linijki rekurencyjnej "mantry":

Dla braku argumentów:

    void Aaaaaaa()
    {
        if (InvokeRequired) { Invoke(new Action(Aaaaaaa)); return; } //1 line of mantra

        // Your code!
    }

Dla funkcji, która ma argumenty:

    void Bbb(int x, string text)
    {
        if (InvokeRequired) { Invoke(new Action<int, string>(Bbb), new[] { x, text }); return; }
        // Your code!
    }

To jest to .


Niektóre argumenty : zwykle źle jest dla czytelności kodu umieszczanie {} po if () w jednej linijce. Ale w tym przypadku jest to rutynowa "mantra". Nie łamie czytelności kodu, jeśli ta metoda jest spójna w projekcie. I zapisuje kod z zaśmiecanie (jedna linijka kodu zamiast pięciu).

Jak widzisz if(InvokeRequired) {something long} po prostu wiesz "ta funkcja jest Bezpieczna do wywołania z innego wątku".

 12
Author: MajesticRa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-26 10:00:26

Musisz użyć invoke i delegate

private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
 11
Author: A. Zalonis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-10 13:55:34

Utwórz zmienną klasy:

SynchronizationContext _context;

Ustaw go w konstruktorze, który tworzy twój UI:

var _context = SynchronizationContext.Current;

Kiedy chcesz zaktualizować Etykietę:

_context.Send(status =>{
    // UPDATE LABEL
}, null);
 11
Author: blackmind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-26 10:10:52

Najprostszy sposób myślę:

   void Update()
   {
       BeginInvoke((Action)delegate()
       {
           //do your update
       });
   }
 6
Author: Vasily Semenov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-18 04:41:41

Na przykład, dostęp do kontrolki innej niż w bieżącym wątku:

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

Tam lblThreshold jest etykietą, a {[2] } jest zmienną globalną.

 6
Author: Da Xiong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-26 10:11:42

Gdy jesteś w wątku UI, możesz poprosić go o harmonogram zadań synchronizacji kontekstu. Dałoby to TaskScheduler , który rozkłada wszystko w wątku UI.

Następnie możesz łańcuchować zadania tak, aby gdy wynik jest gotowy, inne zadanie (zaplanowane w wątku UI) wybrało je i przypisało do etykiety.

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

To działa dla zadań (nie wątków), które są preferowanym sposobem pisania współbieżnego kodu teraz .

 6
Author: nosalan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-26 10:15:32

Właśnie przeczytałem odpowiedzi i wydaje się, że to bardzo gorący temat. Obecnie używam. NET 3.5 SP1 i Windows Forms.

Dobrze znana formuła znacznie opisana w poprzednich odpowiedziach, która wykorzystuje Właściwość InvokeRequired obejmuje większość przypadków, ale nie całą pulę.

Co jeśli Uchwyt nie został jeszcze utworzony?

InvokeRequired właściwość, jak opisano tutaj (Kontrola.InvokeRequired Property reference to MSDN) zwraca true, jeśli wywołanie zostało wykonane z wątku, który nie jest wątkiem GUI, false, jeśli wywołanie zostało wykonane z wątku GUI lub jeśli Uchwyt nie został jeszcze utworzony.

Możesz natknąć się na wyjątek, jeśli chcesz, aby formularz modalny był wyświetlany i aktualizowany przez inny wątek. Ponieważ chcesz, aby ta forma była wyświetlana modalnie, możesz wykonać następujące czynności:

private MyForm _gui;

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

I delegat może zaktualizować Etykietę w 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!";
}

Może to spowodować InvalidOperationException jeśli operacje przed aktualizacją etykiety "zajmują mniej czasu" (odczytują ją i interpretują jako uproszczenie) niż czas potrzebny na utworzenie wątku graficznego do utworzeniaformularza 'SHandle . Dzieje się tak w ramach metody ShowDialog () .

Należy również sprawdzić Uchwyt w następujący sposób:

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

Możesz obsłużyć operację do wykonania, Jeśli Uchwyt nie został jeszcze utworzony: możesz po prostu zignorować GUI update (jak pokazano w powyższym kodzie) lub można poczekać (bardziej ryzykowne). To powinno odpowiedzieć na pytanie.

Rzeczy opcjonalne: Osobiście wpadłem na pomysł:

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

Przesyłam moje formularze, które są aktualizowane przez inny wątek z instancją tego ThreadSafeGuiCommand , i definiuję metody, które aktualizują GUI (w moim formularzu) w następujący sposób:

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

W ten sposób jestem pewien, że mój GUI zostanie zaktualizowany niezależnie od wątku, który wykona połączenie, opcjonalnie czeka na dobrze zdefiniowany czas (timeout).

 6
Author: Sume,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-26 10:20:01

Chciałem dodać ostrzeżenie, ponieważ zauważyłem, że niektóre z prostych rozwiązań pomijają sprawdzanie InvokeRequired.

Zauważyłem, że jeśli twój kod wykona przed utworzeniem uchwytu okna kontrolki (np. przed pokazaniem formularza), Invoke rzuca wyjątek. Dlatego zalecam zawsze sprawdzanie InvokeRequired przed wywołaniem Invoke lub BeginInvoke.

 5
Author: Jos Bosmans,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-06 09:46:30