繁体   English   中英

如何从另一个线程更新 GUI?

[英]How do I update the GUI from another thread?

从另一个Thread更新Label的最简单方法是什么?

  • 我有一个在thread1上运行的Form ,从那里我开始另一个线程( thread2 )。

  • thread2正在处理一些文件时,我想用thread2的当前工作状态更新Form上的Label

我怎么能那样做?

最简单的方法是将匿名方法传递给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上有很多关于编写异步代码的内容,当你想了解它的时候。

对于 .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 class 的扩展方法,这样可以简化调用:

myLabel.SetPropertyThreadSafe("Text", status);

2010 年 5 月 10 日更新:

对于 .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 表达式来允许更清晰、更简单和更安全的语法:

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

现在不仅会在编译时检查属性名称,还会检查属性的类型,因此不可能(例如)将字符串值分配给 boolean 属性,从而导致运行时异常。

不幸的是,这并不能阻止任何人做一些愚蠢的事情,比如传入另一个Control的属性和值,所以下面的代码将很高兴地编译:

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

因此,我添加了运行时检查以确保传入的属性确实属于调用该方法的Control 不完美,但仍然比 .NET 2.0 版本好很多。

如果有人对如何改进此代码以确保编译时安全有任何进一步的建议,请发表评论!

处理长时间的工作

.NET 4.5 和 C# 5.0 开始,您应该在所有区域(包括 GUI)中使用 基于任务的异步模式 (TAP)以及async - await关键字:

TAP 是推荐用于新开发的异步设计模式

而不是异步编程 Model (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. 通知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. async关键字,它允许使用await ,它反过来阻止事件处理程序到达完成 state 直到任务完成,同时不会阻塞 UI 线程。
  4. Progress class(参见IProgress 接口)支持关注点分离 (SoC)设计原则并且不需要显式调度程序和调用。 它使用来自其创建位置(此处为 UI 线程)的当前SynchronizationContext
  5. TaskCreationOptions.LongRunning提示不要将任务排队到ThreadPool中。

有关更详细的示例,请参阅:C# 的未来:约瑟夫·阿尔巴哈里 ( Joseph Albahari ) 的“等待”者会遇到好事。

另请参阅UI 线程 Model概念。

处理异常

下面的代码片段是一个示例,说明如何处理异常和切换按钮的Enabled属性以防止在后台执行期间多次单击。

private async void Button_Click(object sender, EventArgs e)
{
    button.Enabled = false;

    try
    {
        var progress = new Progress<string>(s => button.Text = s);
        await Task.Run(() => SecondThreadConcern.FailingWork(progress));
        button.Text = "Completed";
    }
    catch(Exception exception)
    {
        button.Text = "Failed: " + exception.Message;
    }

    button.Enabled = true;
}

class SecondThreadConcern
{
    public static void FailingWork(IProgress<string> progress)
    {
        progress.Report("I will fail in...");
        Task.Delay(500).Wait();

        for (var i = 0; i < 3; i++)
        {
            progress.Report((3 - i).ToString());
            Task.Delay(500).Wait();
        }

        throw new Exception("Oops...");
    }
}

Marc Gravell 针对 .NET 4 的最简单解决方案的变体:

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

或者改用 Action 委托:

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

请参阅此处了解两者的比较: MethodInvoker vs Action for Control.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;
        }
    }
}

您的工作线程有一个事件。 您的 UI 线程启动另一个线程来完成工作并连接该工作事件,以便您可以显示工作线程的 state。

然后在 UI 中,您需要跨线程来更改实际控件...例如 label 或进度条。

简单的解决方案是使用Control.Invoke

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

void updateGUI() {
    // update gui here
}

线程代码通常有问题并且总是难以测试。 您不需要编写线程代码来从后台任务更新用户界面。 只需使用BackgroundWorker class 来运行任务及其ReportProgress方法来更新用户界面。 通常,您只报告完成百分比,但还有另一个重载,包括 state object。这是一个仅报告字符串 object 的示例:

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

如果您总是想更新同一个字段,那很好。 如果您要进行更复杂的更新,您可以定义一个 class 来表示 UI state 并将其传递给 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之前关闭表单(请记住, thisForm对象),则可能会触发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 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 以及所有其他平台,您可以查看AsyncOperationAsyncOperationManagerSynchronizationContext类。

为了以这种方式轻松引发事件,我创建了一个扩展方法,它允许我通过调用简化引发事件:

MyEvent.Raise(this, EventArgs.Empty);

当然你也可以借助BackGroundWorker class,它会帮你把这件事抽象出来。

由于场景的琐碎性,我实际上会让 UI 线程轮询状态。 我想你会发现它可以非常优雅。

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.InvokeISynchronizeInvoke.BeginInvoke方法时所需的封送处理操作。 使用编组技术没有错,但您需要注意一些注意事项。

  • 确保不要过于频繁地调用BeginInvoke ,否则它可能会超出消息泵的范围。
  • 在工作线程上调用Invoke是一个阻塞调用。 它将暂时停止在该线程中完成的工作。

我在这个答案中提出的策略颠倒了线程的通信角色。 UI 线程轮询数据,而不是工作线程推送数据。 这是许多场景中使用的常见模式。 由于您只想显示来自工作线程的进度信息,那么我认为您会发现此解决方案是封送处理解决方案的绝佳替代方案。 它具有以下优点。

  • 与将它们紧密耦合的Control.InvokeControl.BeginInvoke方法相反,UI 和工作线程保持松散耦合。
  • UI线程不会阻碍工作线程的进行。
  • 工作线程不能支配 UI 线程更新所花费的时间。
  • UI 和工作线程执行操作的时间间隔可以保持独立。
  • 工作线程不能超出 UI 线程的消息泵。
  • UI 线程决定 UI 更新的时间和频率。

您需要在 GUI 线程上调用该方法。 您可以通过调用 Control.Invoke 来实现。

例如:

delegate void UpdateLabelDelegate (string message);

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

    MyLabelControl.Text = message;
}

前面答案中的 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
}

这个和上面使用.NET Framework 3.0的方案类似,但是解决了compile-time safety support的问题。

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,在搜索了这个问题之后,我发现FrankGOregon Ghost的答案对我来说是最简单最有用的。 现在,我用 Visual Basic 编写代码并通过转换器运行此代码段; 所以我不太确定结果如何。

我有一个名为form_Diagnostics,它有一个名为updateDiagWindow,的富文本框,我将其用作一种日志显示。 我需要能够从所有线程更新其文本。 额外的行允许 window 自动滚动到最新的行。

因此,我现在可以在整个程序的任何地方以您认为无需任何线程即可工作的方式用一行更新显示:

  form_Diagnostics.updateDiagWindow(whatmessage);

主代码(将其放入表单的 class 代码中):

#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()

当我遇到同样的问题时,我向谷歌寻求帮助,但没有给我一个简单的解决方案,而是通过给出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
}

您可以在这样的新线程中调用此 function

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

不要与Thread(() =>.....)混淆。 我在线程上工作时使用匿名 function 或 lambda 表达式。 要减少代码行,您也可以使用ThreadStart(..)方法,我不应该在这里解释。

对于许多用途,它就像这样简单:

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

“serviceGUI()”是表单 (this) 中的 GUI 级别方法,可以根据需要更改任意数量的控件。 从另一个线程调用“updateGUI()”。 可以添加参数来传递值,或者(可能更快)使用 class scope 变量,如果访问它们的线程之间可能发生冲突可能导致不稳定,则根据需要对它们进行锁定。 如果非 GUI 线程时间紧迫(牢记 Brian Gideon 的警告),请使用 BeginInvoke 而不是 Invoke。

这是我的 Ian Kemp 解决方案的 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. 它提高了 static 类型安全性。

否则,原始版本是一个非常好的解决方案。

在这个问题上,大多数其他答案对我来说有点复杂(我是 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";
});

只需使用这样的东西:

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

您可以使用已经存在的委托Action

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

我的版本是插入一行递归“咒语”:

对于没有 arguments:

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

        // Your code!
    }

对于具有 arguments 的 function:

    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}您只知道“这个 function 可以安全地从另一个线程调用”。

创建一个 class 变量:

SynchronizationContext _context;

在创建 UI 的构造函数中设置它:

var _context = SynchronizationContext.Current;

当你想更新 label 时:

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

还有另一个通用的控制扩展方法..

首先为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);
    }
}

并从另一个线程这样调用以访问 UI 线程中名为 object1 的控件:

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

..或者像这样

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

尝试使用此刷新 label

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

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

您必须使用调用和委托

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

WPF应用中最简单的方法是:

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

当您在 UI 线程中时,您可以向它请求同步上下文任务调度程序。 它会给你一个TaskScheduler来安排 UI 线程上的所有事情。

然后你可以链接你的任务,这样当结果准备好时,另一个任务(在 UI 线程上安排)选择它并将它分配给 label。

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

这适用于现在编写并发代码的首选方式的任务(不是线程)。

例如访问当前线程以外的控件:

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

lblThreshold是 Label, Speed_Threshold是一个全局变量。

从另一个Thread更新Label的最简单方法是什么?

  • 我有一个在thread1上运行的Form ,然后我正在启动另一个线程( thread2 )。

  • thread2正在处理一些文件时,我想用thread2工作的当前状态更新Form上的Label

我怎么能那样做?

我刚刚阅读了答案,这似乎是一个非常热门的话题。 我目前正在使用 .NET 3.5 SP1 和 Windows Forms。

在前面的答案中详细描述的使用InvokeRequired属性的众所周知的公式涵盖了大多数情况,但不是整个池。

如果Handle还没有创建呢?

InvokeRequired属性,如此处所述(Control.InvokeRequired Property reference to MSDN)如果调用是从不是 GUI 线程的线程进行的,则返回 true,如果调用是从 GUI 线程进行的,或者Handle是尚未创建。

如果您想让另一个线程显示和更新模态表单,您可能会遇到异常。 因为您希望以模态方式显示该表单,您可以执行以下操作:

private MyForm _gui;

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

并且代表可以在 GUI 上更新 Label:

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

如果标签更新之前的操作“花费更少的时间”(阅读它并将其解释为简化)比 GUI 线程创建FormHandle所需的时间,这可能会导致InvalidOperationException 这发生在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!";
}

如果Handle尚未创建,您可以处理要执行的操作:您可以忽略 GUI 更新(如上面的代码所示),或者您可以等待(风险更大)。 这应该回答这个问题。

可选的东西:我个人想出了以下代码:

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

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

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

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

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

我喂我的 forms 由另一个线程用这个ThreadSafeGuiCommand的实例更新,我定义了更新 GUI 的方法(在我的表单中),如下所示:

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

通过这种方式,我很确定我将更新我的 GUI,无论线程将进行调用,可选地等待明确定义的时间量(超时)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM