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

How do I update the GUI from another thread?

提问人:CruelIO 提问时间:3/19/2009 最后编辑:LopDevCruelIO 更新时间:10/9/2022 访问量:827365

问:

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

  • 我有一个运行,然后我从那里开始另一个线程()。Formthread1thread2

  • 在处理一些文件时,我想用当前工作状态更新一个。thread2LabelFormthread2

我该怎么做?

C# .NET 多线程 WinForms 用户界面

评论

15赞 MichaelD 1/19/2010
也许有点晚了:codeproject.com/KB/cs/Threadsafe_formupdating.aspx
28赞 Preet Sangha 3/19/2009
.net 2.0+ 不是专门为此而拥有 BackgroundWorker 类吗?它 UI 线程感知。1. 创建 BackgroundWorker 2.添加两个委托(一个用于处理,一个用于完成)
4赞 Ryszard Dżegan 8/3/2013
请参阅 .NET 4.5 和 C# 5.0 的答案:stackoverflow.com/a/18033198/2042090
5赞 hlovdal 4/2/2014
这个问题不适用于Gtk# GUI。对于 Gtk#,请参阅这个这个答案。
3赞 Marc L. 7/12/2018
请注意:这个问题的答案现在是一堆杂乱无章的 OT(“这是我为我的 WPF 应用程序所做的”)和历史 .NET 2.0 工件。

答:

43赞 Frederik Gheysels 3/19/2009 #1

您必须确保更新发生在正确的线程上;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 窗体和 WPF 以及所有其他平台,您可以查看 和 类。AsyncOperationAsyncOperationManagerSynchronizationContext

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

MyEvent.Raise(this, EventArgs.Empty);

当然,您也可以使用 BackGroundWorker 类,它将为您抽象化这个问题。

评论

0赞 Frederik Gheysels 3/19/2009
确实如此,但我不喜欢用这件事来“弄乱”我的 GUI 代码。我的 GUI 不应该关心它是否需要调用。换句话说:我不认为执行上下文 swithc 是 GUI 的责任。
1赞 Marc Gravell 3/19/2009
将委托分开等似乎有点矫枉过正 - 为什么不只是: SynchronizationContext.Current.Send(delegate { MyEvent(...); }, null);
0赞 Frederik Gheysels 3/19/2009
您是否始终可以访问 SynchronizationContext ?即使你的班级在班级库中?
69赞 OregonGhost 3/19/2009 #2

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

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

void updateGUI() {
    // update gui here
}

评论

0赞 MBH 12/19/2015
干得好,简单!不仅简单,而且效果很好!我真的不明白为什么微软不能让它变得更简单!为了在主线程上调用 1 行,我们应该编写几个函数!
1赞 ToolmakerSteve 5/17/2016
@MBH同意。顺便说一句,你有没有注意到上面 stackoverflow.com/a/3588137/199364 答案,它定义了一个扩展方法?在自定义实用程序类中执行此操作一次,然后不必再关心Microsoft没有为我们执行此操作:)
0赞 MBH 5/17/2016
@ToolmakerSteve 这正是它的意思!你是对的,我们可以找到一种方法,但我的意思是从 DRY(不要重复自己)的角度来看,具有共同解决方案的问题可以通过Microsoft以最小的努力由他们解决,这将为程序员节省大量时间:)
32赞 Kieron 3/19/2009 #3

您需要在 GUI 线程上调用该方法。可以通过调用 Control.Invoke 来执行此操作。

例如:

delegate void UpdateLabelDelegate (string message);

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

    MyLabelControl.Text = message;
}

评论

1赞 CruelIO 3/19/2009
invoke 行给了我一个编译器错误。“System.Windows.Forms.Control.Invoke(System.Delegate, object[])”的最佳重载方法匹配有一些无效参数
1213赞 Marc Gravell 3/19/2009 #4

最简单的方法是将匿名方法传递给 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

请注意,在执行完成之前会阻止执行 - 这是同步代码。这个问题没有问异步代码,但 Stack Overflow 上有很多关于编写异步代码的内容,当你想了解它时。Invoke

评论

8赞 Marc Gravell 3/19/2009
鉴于 OP 除了表单之外没有提到任何类/实例,这并不是一个糟糕的默认值......
42赞 AZ. 3/17/2010
不要忘记“this”关键字引用了“Control”类。
8赞 Marc Gravell 12/2/2011
@codecompleting无论哪种方式都是安全的,而且我们已经知道我们在工人身上,那么为什么要检查我们知道的东西呢?
5赞 Marc Gravell 2/17/2012
@Dragouf不是真的 - 使用此方法的要点之一是您已经知道哪些部分在工作线程上运行,哪些部分在 UI 线程上运行。无需检查。
3赞 Marc Gravell 12/12/2013
@John因为这是 Control.Invoke 对任何委托所做的 - 而不仅仅是 anon 方法
72赞 Hath 3/19/2009 #5

这是您应该执行此操作的经典方法:

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 线程启动另一个线程来执行工作,并挂接该工作线程事件,以便您可以显示工作线程的状态。

然后在UI中,你需要跨线程来改变实际的控制......就像标签或进度条一样。

823赞 Ian Kemp 3/19/2009 #6

对于 .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);

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

它使用 LINQ 和 lambda 表达式来允许更干净、更简单和更安全的语法:

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

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

不幸的是,这并不能阻止任何人做愚蠢的事情,例如传入另一个人的属性和值,因此以下内容将愉快地编译:Control

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

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

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

评论

3赞 Corvin 1/31/2011
在某些情况下,这。GetType() 的计算结果与 propertyInfo.ReflectedType 相同(例如,WinForms 上的 LinkLabel)。我没有大量的 C# 经验,但我认为异常的条件应该是: if (propertyInfo == null ||([电子邮件保护]()。IsSubclassOf(propertyInfo.ReflectedType) & & @this。GetType() != propertyInfo.ReflectedType) ||@this。GetType() 中。GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null)
9赞 Smith 6/23/2011
@lan可以从另一个模块、类或表单调用它SetControlPropertyThreadSafe(myLabel, "Text", status)
85赞 Frank Hileman 3/29/2014
提供的解决方案非常复杂。如果您重视简单性,请参阅 Marc Gravell 的解决方案或 Zaid Masud 的解决方案。
8赞 quadroid 8/6/2014
如果您更新多个属性,此解决方案确实会浪费大量资源,因为每个 Invoke 都会消耗大量资源。无论如何,我不认为这就是线程安全功能的意图。封装您的 UI 更新操作并调用它一次(而不是每个属性)
4赞 Andy 10/9/2016
你到底为什么要在 BackgroundWorker 组件上使用此代码?
22赞 Frankg 8/24/2010 #7

对于许多目的,它就是这么简单:

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

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

34赞 5 revs, 2 users 93%Brian Gideon #8

由于该方案的琐碎性,我实际上会让 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...";
    }
  }
}

该方法避免了使用 and 方法时所需的封送处理操作。使用封送处理技术没有错,但您需要注意一些注意事项。ISynchronizeInvoke.InvokeISynchronizeInvoke.BeginInvoke

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

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

  • UI 和工作线程保持松散耦合,而不是将它们紧密耦合的 or 方法。Control.InvokeControl.BeginInvoke
  • UI 线程不会妨碍工作线程的进度。
  • 工作线程不能控制 UI 线程更新所花费的时间。
  • UI 和工作线程执行操作的时间间隔可以保持独立。
  • 工作线程无法溢出 UI 线程的消息泵。
  • UI 线程可以决定 UI 更新的时间和频率。

评论

3赞 Matt 11/11/2013
好主意。您唯一没有提到的是,一旦 WorkerThread 完成,您如何正确处理计时器。请注意,这可能会在应用程序结束时(即用户关闭应用程序)时造成麻烦。您知道如何解决这个问题吗?
0赞 Phil1970 4/20/2018
@Matt 您不使用事件的匿名处理程序,而是使用成员方法,以便在释放表单时删除计时器...Elapsed
0赞 Matt 4/20/2018
@Phil1970 - 好点子。你的意思是喜欢并通过 分配它,稍后在处置上下文中做一个我说得对吗?并按照此处讨论的建议进行处置/关闭。System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };m_Timer.Elapsed += handler;m_Timer.Elapsed -= handler;
51赞 Don Kirkby 8/28/2010 #9

线程代码通常有问题,并且总是难以测试。无需编写线程代码即可从后台任务更新用户界面。只需使用 BackgroundWorker 类来运行任务,并使用其 ReportProgress 方法来更新用户界面。通常,您只报告完成百分比,但还有另一个包含状态对象的重载。下面是一个仅报告字符串对象的示例:

    private void button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.WorkerReportsProgress = true;
        backgroundWorker1.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "A");
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "B");
        Thread.Sleep(5000);
        backgroundWorker1.ReportProgress(0, "C");
    }

    private void backgroundWorker1_ProgressChanged(
        object sender, 
        ProgressChangedEventArgs e)
    {
        label1.Text = e.UserState.ToString();
    }

如果您总是想更新相同的字段,这很好。如果要进行更复杂的更新,可以定义一个类来表示 UI 状态,并将其传递给 ReportProgress 方法。

最后一件事,一定要设置标志,否则该方法将被完全忽略。WorkerReportsProgressReportProgress

评论

2赞 DavidRR 4/2/2016
在处理结束时,还可以通过 更新用户界面。backgroundWorker1_RunWorkerCompleted
154赞 StyxRiver 8/28/2010 #10

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

评论

5赞 argyle 9/9/2013
@this用法的意义何在?难道“控制”不是等价的吗?@this有什么好处吗?
16赞 StyxRiver 9/12/2013
@jeromeyers - 只是变量名称,在本例中是对调用扩展的当前控件的引用。你可以把它重命名为source,或者任何漂浮你的船的东西。我使用 ,因为它指的是调用扩展的“this Control”,并且(至少在我的脑海中)与在普通(非扩展)代码中使用“this”关键字是一致的。@this@this
2赞 Auto 2/24/2016
这很棒,很简单,对我来说是最好的解决方案。您可以在 ui 线程中包含您必须执行的所有工作。示例:this。UIThread(() => { txtMessage.Text = message; listBox1.Items.Add(message);
1赞 ToolmakerSteve 5/17/2016
我真的很喜欢这个解决方案。小问题:我会将此方法命名为 .OnUIThreadUIThread
5赞 Grisgram 1/30/2019
这就是为什么我命名了这个扩展名。但这只是个人品味。RunOnUiThread
26赞 Francis 3/2/2011 #11

这与上面使用 .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");
21赞 Rotaerk 9/16/2011 #12

这是我的 C# 3.0 变体 Ian Kemp 的解决方案:

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. 它将 null 检查添加到“as MemberExpression”的结果中。
  2. 它提高了静态类型的安全性。

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

26赞 bgmCoder 12/18/2011 #13

萨尔维特!搜索了这个问题后,我发现 FrankGOregon Ghost 的答案对我来说是最简单、最有用的。现在,我在 Visual Basic 中编写代码,并通过转换器运行此代码段;所以我不确定结果如何。

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

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

  form_Diagnostics.updateDiagWindow(whatmessage);

主代码(将其放在表单的类代码中):

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

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

对于没有参数:

    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}

23赞 ILoveFortran 5/28/2012 #15
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()

274赞 Zaid Masud 5/30/2012 #16

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

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

或者改用 Action 委托:

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

有关两者的比较,请参阅此处:Control.BeginInvoke 的 MethodInvoker 与 Action

评论

1赞 Dbloom 2/10/2017
这个例子中的“控制”是什么?我的 UI 控件?尝试在标签控件上的 WPF 中实现这一点,并且 Invoke 不是我的标签的成员。
0赞 Kiquenet 4/3/2017
像@styxriver stackoverflow.com/a/3588137/206730 这样的扩展方法怎么样?
0赞 Antonio Leite 8/25/2017
在类或方法中声明“Action y;”,更改 text 属性,并使用这段代码“yourcontrol”更新文本。Invoke(y=() =>您的控件。text = “新文本”);'
8赞 slow 3/22/2018
@Dbloom它不是成员,因为它仅适用于 WinForms。对于 WPF,请使用 Dispatcher.Invoke
5赞 Rakibul Haq 4/5/2018
我正在遵循此解决方案,但有时我的 UI 没有更新。我发现我需要强制无效并重新绘制 GUI.如果有帮助..this.refresh()
16赞 Embedd_0913 7/28/2012 #17

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

private void UpdateMethod()
{
    if (InvokeRequired)
    {
        Invoke(new Action(UpdateMethod));
    }
}
22赞 ahmar 10/24/2012 #18

当我遇到同样的问题时,我向谷歌寻求帮助,但并没有给我一个简单的解决方案,而是通过举例等等等等,让我更加困惑。所以我决定自己解决它。这是我的解决方案: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();

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

30赞 Jon H 5/31/2013 #19

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

评论

4赞 increddibelly 5/5/2016
您认为 Post 方法在后台使用了什么?:)
13赞 user1360355 6/17/2013 #20

尝试使用此刷新标签

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

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

评论

0赞 Kiquenet 4/3/2017
是针对 Windows 窗体的吗
465赞 Ryszard Dżegan 8/3/2013 #21

处理长时间的工作

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

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

有关更详细的示例,请参阅:Joseph AlbahariThe Future of C#: Good things come to those who 'await'

另请参阅有关 UI 线程模型概念的信息。

处理异常

下面的代码片段是一个示例,说明如何处理异常和切换按钮的属性,以防止在后台执行期间多次单击。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...");
    }
}

评论

2赞 kdbanman 8/14/2015
如果抛出异常,UI线程可以捕获吗?顺便说一句,这是一个很好的帖子。SecondThreadConcern.LongWork()
2赞 Ryszard Dżegan 8/14/2015
我在答案中添加了一个额外的部分,以满足您的要求。问候。
3赞 Ryszard Dżegan 8/18/2015
ExceptionDispatchInfo 类负责在 async-await 模式下在 UI 线程上重新抛出背景异常的奇迹。
1赞 MeTitus 9/28/2015
难道只是我认为这种方式比仅仅调用 Invoke/Begin 更冗长吗?!
4赞 Yarik 12/16/2016
Task.Delay(500).Wait()?创建一个任务来阻止当前线程有什么意义?永远不应该阻止线程池线程!
13赞 A. Zalonis 9/10/2013 #22

必须使用 invoke 和 delegate

private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
3赞 user523650 1/27/2014 #23

我更喜欢这个:

private void UpdateNowProcessing(string nowProcessing)
{
    if (this.InvokeRequired)
    {
        Action<string> d = UpdateNowProcessing;
        Invoke(d, nowProcessing);
    }
    else
    {
        this.progressDialog.Next(nowProcessing);
    }            
}
6赞 Jos Bosmans 2/6/2014 #24

我想添加警告,因为我注意到一些简单的解决方案省略了检查。InvokeRequired

我注意到,如果您的代码在创建控件的窗口句柄之前(例如,在显示窗体之前)执行,则会引发异常。因此,我建议在致电或之前始终进行检查。InvokeInvokeRequiredInvokeBeginInvoke

8赞 Vasily Semenov 2/18/2014 #25

我认为最简单的方法:

   void Update()
   {
       BeginInvoke((Action)delegate()
       {
           //do your update
       });
   }
15赞 blackmind 2/20/2014 #26

创建一个类变量:

SynchronizationContext _context;

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

var _context = SynchronizationContext.Current;

当您想要更新标签时:

_context.Send(status =>{
    // UPDATE LABEL
}, null);
9赞 Da Xiong 3/19/2014 #27

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

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

那里有一个 Label,是一个全局变量。lblThresholdSpeed_Threshold

6赞 user3592198 5/1/2014 #28

也许有点过量,但这是我通常解决这个问题的方法:

由于同步,此处不需要调用。 BasicClassThreadExample 对我来说只是一种布局,因此请更改它以满足您的实际需求。

这很简单,因为您不需要处理 UI 线程中的内容!

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

}
10赞 nan 5/12/2014 #29

当您在 UI 线程中时,您可以要求它提供其同步上下文任务计划程序。它会给你一个 TaskScheduler,用于调度 UI 线程上的所有内容。

然后,您可以链接您的任务,以便在结果准备就绪时,另一个任务(在 UI 线程上计划)选取它并将其分配给标签。

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

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

评论

1赞 Ohad Schneider 5/24/2014
呼叫通常不是一个好的做法 blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspxTask.Start
51赞 Ohad Schneider 5/24/2014 #30

绝大多数答案都使用这是一个等待发生的竞争条件。例如,考虑公认的答案:Control.Invoke

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

如果用户在调用之前关闭窗体(记住,是对象),则可能会触发。this.InvokethisFormObjectDisposedException

解决方案是使用 ,特别是 hamilton.danielb 建议的(其他答案依赖于完全不必要的特定实现)。我会稍微修改他的代码以使用而不是通过(因为通常不需要工作线程等待):SynchronizationContextSynchronizationContext.CurrentSynchronizationContextSynchronizationContext.PostSynchronizationContext.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 及更高版本上,您还可以使用 Ryszard Dżegan 所演示的(基本上是在其创建时捕获的),用于长时间运行的操作需要运行 UI 代码同时仍在工作的情况。Progress<T>SynchronizationContext.Current

评论

0赞 AaA 9/20/2022
既然_context是一个变量,如果把它释放掉,你的解决方案会不会也失败了?Myform
0赞 Ohad Schneider 9/21/2022
@AaA否,因为处置不会对 做任何事情,它仍然是(只要 WinForms 应用程序正在运行,它就会始终有效)MyForm_contextSynchronizationContext.Current
0赞 AaA 9/22/2022
这是正确的,但是如果 MyForm 被释放了,someLabel 不也会被释放吗?
1赞 Ohad Schneider 9/22/2022
@AaA 我相信这里的假设是一个普通的 WinForms 应用程序,如果表单被释放,它基本上意味着 WinForms 应用程序正在关闭,因此不会在消息循环上运行您的委托(否则同步上下文解决方案无论如何都不会安全)。如果你的场景不同,我想你可以添加一个检查,比如(安全,因为此时你正在 UI 线程上运行,特别是阻止消息泵处理标签)。if (someLabel.IsDisposed) { someLabel.Text = newText }
0赞 AaA 10/22/2022
没错,我有一个应用程序,每 10k 次运行中就有 1 次会丢弃异常日志,该组件已被释放且无法访问(或类似消息),无论如何,如果有人在他们的日志中不需要 10,000 条消息中的一条,特别是当您的老板惊慌失措地看到日志中的异常时,应该检查 IsDisposed!
0赞 Sukhdevsinh Zala 7/2/2014 #31

为了在 WPF 中实现这一点,我采用以下方式。

 new Thread(() => 
 {
     while (...)
     {
         SomeLabel.Dispatcher.BeginInvoke((Action)(() => SomeLabel.Text = ...));
     }
 }).Start();

评论

5赞 Andrew Barber 7/3/2014
顺便说一句,这个问题实际上是关于 [winforms] 的。
8赞 Sume 11/6/2014 #32

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

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

如果尚未创建句柄怎么办?

如果调用是从不是 GUI 线程的线程发出的,则 InvokeRequired 属性(如此处所述)(对 MSDN 的 Control.InvokeRequired 属性引用)返回 true;如果调用是从 GUI 线程发出的,或者尚未创建句柄,则返回 false。

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

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 线程创建窗体句柄所需的时间(读取它并将其解释为简化),则这可能会导致 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!";
}

如果尚未创建句柄,则可以处理要执行的操作:您可以忽略 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,无论哪个线程将进行调用,都可以选择等待明确定义的时间(超时)。

评论

1赞 Jon 2/12/2016
来这里是为了找到这个,因为我也检查了 IsHandleCreated。另一个要检查的属性是 IsDisposed。如果表单已释放,则无法对其调用 Invoke()。如果用户在后台线程完成之前关闭了窗体,则不希望它在释放窗体时尝试回调 UI。
0赞 Phil1970 12/18/2016
我想说,一开始是个坏主意......通常,您会立即显示子窗体,并在进行后台处理时获得进度条或其他一些反馈。或者,您将先进行所有处理,然后在创建时将结果传递给新表单。同时执行这两项操作通常会带来边际收益,但代码的可维护性要低得多。
0赞 Sume 3/30/2017
所描述的方案考虑了用作后台线程作业进度视图的模态窗体。因为它必须是模态的,所以必须通过调用 Form.ShowDialog() 方法来显示它。通过执行此操作,可以防止在关闭窗体之前执行调用后面的代码。因此,除非您可以以不同于给定示例的方式启动后台线程(当然,也可以),否则必须在后台线程启动后以模式方式显示此表单。在这种情况下,您需要检查是否要创建句柄。如果你不需要模态形式,那就另当别论了。
6赞 Roman Ambinder 1/31/2015 #33

基本上,无论框架版本或 GUI 基础库类型如何,解决此问题的方法是保存控件,为工作线程创建线程的同步上下文,该上下文会将控件的相关交互从工作线程封送到 GUI 的线程消息队列。

例:

SynchronizationContext ctx = SynchronizationContext.Current; // From control
ctx.Send\Post... // From worker thread
2赞 ipe 8/8/2015 #34

就我而言(WPF),解决方案很简单:

private void updateUI()
{
    if (!Dispatcher.CheckAccess())
    {
        Dispatcher.BeginInvoke(updateUI);
        return;
    }

    // Update any number of controls here
}

评论

0赞 Marc L. 7/11/2018
问题针对 Winforms。
9赞 Yuliia Ashomok 11/1/2015 #35

即使操作很耗时(在我的示例中为 thread.sleep) - 此代码也不会锁定您的 UI:

 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; 
     });
 }
7赞 MBH 12/19/2015 #36

我无法理解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 */ }));
18赞 Hassan Shouman 1/12/2016 #37

只需使用如下内容:

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

评论

0赞 LarsTech 1/12/2016
如果有,您不是已经从调用此方法的 UI 线程中了吗?e.ProgressPercentage
0赞 LarsTech 1/12/2016
ProgressChanged 事件在 UI 线程上运行。这是使用 BackgroundWorker 的便利之一。Completed 事件也在 gui 上运行。在非 UI 线程中运行的唯一内容是 DoWork 方法。
6赞 JWP 5/16/2016 #38

这是一个使用更实用的风格对一个古老问题的新看法。如果将 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;
    }
}

评论

0赞 Marc L. 7/11/2018
真的要问:像这样的包装与仅仅调用有什么不同?间接的优点是什么?Task.Runawait Task.Run(() => {...});
0赞 JWP 7/11/2018
表面上没有区别。更深入地看,它显示了函数式编程的力量。特别是,它包装了一种静态方法,有助于单一责任。如果您现在想要实现 ConfigureAwait(false) 或添加日志记录语句,该怎么办?您现在只需执行一次。
12赞 Basheer AL-MOMANI 6/27/2016 #39

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

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

评论

4赞 Gertjan Gielen 9/27/2016
如果您使用的是 WPF 应用程序,这是正确的。但他使用的是 Windows 窗体。
0赞 Francis 4/10/2019
您甚至可以在 Winforms 应用程序中使用 Dispatcher。stackoverflow.com/questions/303116/......
19赞 Manohar Reddy Poreddy 11/18/2016 #40

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

评论

0赞 Marc L. 7/11/2018
问题是关于 Winforms,而不是 WPF。
0赞 Manohar Reddy Poreddy 7/11/2018
谢谢。在上面添加了 WinForms 解决方案。
0赞 Marc L. 7/11/2018
...这只是关于同一问题的许多其他答案的副本,但没关系。为什么不成为解决方案的一部分,直接删除您的答案呢?
0赞 Manohar Reddy Poreddy 7/11/2018
嗯,你是对的,如果只是,你仔细阅读了我的答案,开头部分(我写答案的原因),希望多一点注意力,你会看到有人今天有完全相同的问题并为我的简单答案投了赞成票,如果你能预见到为什么这一切发生的真实故事,那就更受欢迎了, 即使我搜索 WPF,Google 也会把我送到这里。当然,既然你错过了这或多或少明显的 3 个原因,我可以理解为什么你不会删除你的反对票。与其清理好的,不如创造一些新的东西,这要困难得多。
5赞 Musculaa 1/18/2017 #41

首先获取窗体的实例(在本例中为 mainForm),然后在另一个线程中使用此代码。

mainForm.Invoke(new MethodInvoker(delegate () 
{
    // Update things in my mainForm here
    mainForm.UpdateView();
}));
6赞 Alexander Egorov 3/5/2017 #42

关于该主题的另一个示例:我创建了一个抽象类 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
});
4赞 Saurabh 3/25/2017 #43

将一些公共变量放在一个单独的类中来保存该值。

例:

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";
    }
}
0赞 Lankan 2/28/2018 #44

最简单的方法是调用如下:

 Application.Current.Dispatcher.Invoke(new Action(() =>
             {
                    try
                    {
                        ///
                    }
                    catch (Exception)
                    {
                      //
                    }


                    }
     ));

评论

1赞 LarsTech 3/1/2018
除了问题针对 WinForms。
0赞 siggi_pop 11/14/2018
不知道为什么这被否决了!?它帮助我解决了 WPF 应用程序中的问题。并非这篇文章的所有读者都会遇到与 OP 完全相同的问题,有时解决方案的一部分可以在更短的时间内帮助读者,然后完整地解决一个独特的问题。
15赞 flodis 11/18/2018 #45

还有另一个通用的 Control 扩展 aproach..

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

评论

0赞 Mecanik 3/12/2021
非常优雅,非常好!
0赞 flodis 3/14/2021
我已经开始使用 c.BeginInvoke 进行异步更新。如果在级联中调用,则不太可能导致死锁。
3赞 CSDev 4/11/2019 #46

一般方法如下:

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

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        int clickCount = 0;

        public Form1()
        {
            InitializeComponent();
            label1.SetText("0");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            new Thread(() => label1.SetText((++clickCount).ToString())).Start();
        }
    }

    public static class ControlExtensions
    {
        public static void SetText(this Control control, string text)
        {
            if (control.InvokeRequired)
                control.Invoke(setText, control, text);
            else
                control.Text = text;
        }

        private static readonly Action<Control, string> setText =
            (control, text) => control.Text = text;
    }
}

说明

答案很像这个。但是使用更整洁(对我来说)和更新的语法。该点是 的属性。它获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 invoke 方法,因为调用方与创建控件的线程位于不同的线程上。因此,如果我们调用了创建相同的线程,则可以将其设置为 这样。但是在任何其他线程上,它会导致,因此必须调用一个方法 via 在创建线程上设置。InvokeRequiredcontrolcontrol.SetText("some text")controlTextcontrol.Text = textSystem.InvalidOperationExceptioncontrol.Invoke(...)Textcontrol

8赞 user53373 4/11/2019 #47

只需使用UI的同步上下文

using System.Threading;

// ...

public partial class MyForm : Form
{
    private readonly SynchronizationContext uiContext;

    public MyForm()
    {
        InitializeComponent();
        uiContext = SynchronizationContext.Current; // get ui thread context
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Thread t = new Thread(() =>
            {// set ui thread context to new thread context                            
             // for operations with ui elements to be performed in proper thread
             SynchronizationContext
                 .SetSynchronizationContext(uiContext);
             label1.Text = "some text";
            });
        t.Start();
    }
}

评论

0赞 user53373 4/12/2019
当然可以。我为此添加了评论。
0赞 Sidhin S Thomas 8/8/2020
这是IMO最直接,最易读的方法。