提问人:CruelIO 提问时间:3/19/2009 最后编辑:LopDevCruelIO 更新时间:10/9/2022 访问量:827365
如何从另一个线程更新 GUI?
How do I update the GUI from another thread?
问:
从另一个更新的最简单方法是什么?Label
Thread
我有一个运行,然后我从那里开始另一个线程()。
Form
thread1
thread2
在处理一些文件时,我想用当前工作状态更新一个。
thread2
Label
Form
thread2
我该怎么做?
答:
您必须确保更新发生在正确的线程上;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 以及所有其他平台,您可以查看 和 类。AsyncOperation
AsyncOperationManager
SynchronizationContext
为了以这种方式轻松引发事件,我创建了一个扩展方法,它允许我通过调用以下命令来简化引发事件的过程:
MyEvent.Raise(this, EventArgs.Empty);
当然,您也可以使用 BackGroundWorker 类,它将为您抽象化这个问题。
评论
简单的解决方案是使用 .Control.Invoke
void DoSomething()
{
if (InvokeRequired) {
Invoke(new MethodInvoker(updateGUI));
} else {
// Do Something
updateGUI();
}
}
void updateGUI() {
// update gui here
}
评论
您需要在 GUI 线程上调用该方法。可以通过调用 Control.Invoke 来执行此操作。
例如:
delegate void UpdateLabelDelegate (string message);
void UpdateLabel (string message)
{
if (InvokeRequired)
{
Invoke (new UpdateLabelDelegate (UpdateLabel), message);
return;
}
MyLabelControl.Text = message;
}
评论
最简单的方法是将匿名方法传递给 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
评论
这是您应该执行此操作的经典方法:
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中,你需要跨线程来改变实际的控制......就像标签或进度条一样。
对于 .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
如果有人对如何改进此代码以提高编译时安全性有任何进一步的建议,请发表评论!
评论
SetControlPropertyThreadSafe(myLabel, "Text", status)
对于许多目的,它就是这么简单:
public delegate void serviceGUIDelegate();
private void updateGUI()
{
this.Invoke(new serviceGUIDelegate(serviceGUI));
}
“serviceGUI()” 是窗体 (this) 中的一个 GUI 级别方法,可以根据需要更改任意数量的控件。从另一个线程调用“updateGUI()”。可以添加参数来传递值,或者(可能更快)根据需要使用带有锁的类范围变量,如果访问它们的线程之间可能发生冲突,从而导致不稳定。如果非 GUI 线程对时间要求很高,请使用 BeginInvoke 而不是 Invoke(牢记 Brian Gideon 的警告)。
由于该方案的琐碎性,我实际上会让 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.Invoke
ISynchronizeInvoke.BeginInvoke
- 请确保不要过于频繁地调用,否则它可能会使消息泵溢出。
BeginInvoke
- 调用工作线程是阻塞调用。它将暂时停止在该线程中完成的工作。
Invoke
我在此答案中提出的策略颠倒了线程的通信角色。UI 线程轮询数据,而不是工作线程推送数据。这是许多方案中使用的常见模式。由于您要做的只是显示来自工作线程的进度信息,因此我认为您会发现此解决方案是封送解决方案的绝佳替代方案。它具有以下优点。
- UI 和工作线程保持松散耦合,而不是将它们紧密耦合的 or 方法。
Control.Invoke
Control.BeginInvoke
- UI 线程不会妨碍工作线程的进度。
- 工作线程不能控制 UI 线程更新所花费的时间。
- UI 和工作线程执行操作的时间间隔可以保持独立。
- 工作线程无法溢出 UI 线程的消息泵。
- UI 线程可以决定 UI 更新的时间和频率。
评论
Elapsed
System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };
m_Timer.Elapsed += handler;
m_Timer.Elapsed -= handler;
线程代码通常有问题,并且总是难以测试。无需编写线程代码即可从后台任务更新用户界面。只需使用 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 方法。
最后一件事,一定要设置标志,否则该方法将被完全忽略。WorkerReportsProgress
ReportProgress
评论
backgroundWorker1_RunWorkerCompleted
.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");
评论
@this
@this
OnUIThread
UIThread
RunOnUiThread
这与上面使用 .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");
这是我的 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!")
- 它将 null 检查添加到“as MemberExpression”的结果中。
- 它提高了静态类型的安全性。
否则,原版是一个非常好的解决方案。
萨尔维特!搜索了这个问题后,我发现 FrankG 和 Oregon 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
我的版本是插入一行递归的“咒语”:
对于没有参数:
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}
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()
Marc Gravell 的 .NET 4 最简单解决方案的变体:
control.Invoke((MethodInvoker) (() => control.Text = "new text"));
或者改用 Action 委托:
control.Invoke(new Action(() => control.Text = "new text"));
有关两者的比较,请参阅此处:Control.BeginInvoke 的 MethodInvoker 与 Action
评论
this.refresh()
您可以使用已经存在的委托:Action
private void UpdateMethod()
{
if (InvokeRequired)
{
Invoke(new Action(UpdateMethod));
}
}
当我遇到同样的问题时,我向谷歌寻求帮助,但并没有给我一个简单的解决方案,而是通过举例等等等等,让我更加困惑。所以我决定自己解决它。这是我的解决方案: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(..)
前面答案中的 Invoke 内容都不是必需的。
您需要查看 WindowsFormsSynchronizationContext:
// In the main thread
WindowsFormsSynchronizationContext mUiContext = new WindowsFormsSynchronizationContext();
...
// In some non-UI Thread
// Causes an update in the GUI thread.
mUiContext.Post(UpdateGUI, userData);
...
void UpdateGUI(object userData)
{
// Update your GUI controls here
}
评论
尝试使用此刷新标签
public static class ExtensionMethods
{
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement)
{
uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
}
}
评论
处理长时间的工作
从 .NET 4.5 和 C# 5.0 开始,您应该在所有区域(包括 GUI)中使用基于任务的异步模式 (TAP) 以及 async-await 关键字:
TAP 是推荐用于新开发的异步设计模式
而不是异步编程模型 (APM) 和基于事件的异步模式 (EAP)(后者包括 BackgroundWorker 类)。
那么,新开发的推荐解决方案是:
事件处理程序的异步实现(是的,仅此而已):
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"; }
通知 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()); } } }
请注意以下几点:
- 以顺序方式编写的简短干净的代码,没有回调和显式线程。
- 任务而不是线程。
- async 关键字,允许使用 await,这反过来又会阻止事件处理程序达到完成状态,直到任务完成,同时不会阻塞 UI 线程。
- 支持关注点分离 (SoC) 设计原则的 Progress 类 (请参阅 IProgress 接口) ,并且不需要显式调度程序和调用。它使用其创建位置(此处为 UI 线程)中的当前 SynchronizationContext。
- TaskCreationOptions.LongRunning,提示不要将任务排入 ThreadPool。
有关更详细的示例,请参阅:Joseph Albahari 的 The 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...");
}
}
评论
SecondThreadConcern.LongWork()
Task.Delay(500).Wait()
?创建一个任务来阻止当前线程有什么意义?永远不应该阻止线程池线程!
必须使用 invoke 和 delegate
private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
我更喜欢这个:
private void UpdateNowProcessing(string nowProcessing)
{
if (this.InvokeRequired)
{
Action<string> d = UpdateNowProcessing;
Invoke(d, nowProcessing);
}
else
{
this.progressDialog.Next(nowProcessing);
}
}
我想添加警告,因为我注意到一些简单的解决方案省略了检查。InvokeRequired
我注意到,如果您的代码在创建控件的窗口句柄之前(例如,在显示窗体之前)执行,则会引发异常。因此,我建议在致电或之前始终进行检查。Invoke
InvokeRequired
Invoke
BeginInvoke
我认为最简单的方法:
void Update()
{
BeginInvoke((Action)delegate()
{
//do your update
});
}
创建一个类变量:
SynchronizationContext _context;
在创建 UI 的构造函数中设置它:
var _context = SynchronizationContext.Current;
当您想要更新标签时:
_context.Send(status =>{
// UPDATE LABEL
}, null);
例如,访问当前线程以外的控件:
Speed_Threshold = 30;
textOutput.Invoke(new EventHandler(delegate
{
lblThreshold.Text = Speed_Threshold.ToString();
}));
那里有一个 Label,是一个全局变量。lblThreshold
Speed_Threshold
也许有点过量,但这是我通常解决这个问题的方法:
由于同步,此处不需要调用。 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); }
}
当您在 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;
}
}
这适用于任务(不是线程),这些任务是现在编写并发代码的首选方式。
评论
Task.Start
绝大多数答案都使用这是一个等待发生的竞争条件。例如,考虑公认的答案:Control.Invoke
string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate {
someLabel.Text = newText; // runs on UI thread
});
如果用户在调用之前关闭窗体(记住,是对象),则可能会触发。this.Invoke
this
Form
ObjectDisposedException
解决方案是使用 ,特别是 hamilton.danielb 建议的(其他答案依赖于完全不必要的特定实现)。我会稍微修改他的代码以使用而不是通过(因为通常不需要工作线程等待):SynchronizationContext
SynchronizationContext.Current
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 及更高版本上,您还可以使用 Ryszard Dżegan 所演示的(基本上是在其创建时捕获的),用于长时间运行的操作需要运行 UI 代码同时仍在工作的情况。Progress<T>
SynchronizationContext.Current
评论
Myform
MyForm
_context
SynchronizationContext.Current
if (someLabel.IsDisposed) { someLabel.Text = newText }
为了在 WPF 中实现这一点,我采用以下方式。
new Thread(() =>
{
while (...)
{
SomeLabel.Dispatcher.BeginInvoke((Action)(() => SomeLabel.Text = ...));
}
}).Start();
评论
我刚刚阅读了答案,这似乎是一个非常热门的话题。我目前正在使用 .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,无论哪个线程将进行调用,都可以选择等待明确定义的时间(超时)。
评论
基本上,无论框架版本或 GUI 基础库类型如何,解决此问题的方法是保存控件,为工作线程创建线程的同步上下文,该上下文会将控件的相关交互从工作线程封送到 GUI 的线程消息队列。
例:
SynchronizationContext ctx = SynchronizationContext.Current; // From control
ctx.Send\Post... // From worker thread
就我而言(WPF),解决方案很简单:
private void updateUI()
{
if (!Dispatcher.CheckAccess())
{
Dispatcher.BeginInvoke(updateUI);
return;
}
// Update any number of controls here
}
评论
即使操作很耗时(在我的示例中为 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;
});
}
我无法理解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 */ }));
只需使用如下内容:
this.Invoke((MethodInvoker)delegate
{
progressBar1.Value = e.ProgressPercentage; // runs on UI thread
});
评论
e.ProgressPercentage
这是一个使用更实用的风格对一个古老问题的新看法。如果将 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;
}
}
评论
Task.Run
await Task.Run(() => {...});
WPF 应用程序中最简单的方法是:
this.Dispatcher.Invoke((Action)(() =>
{
// This refers to a form in a WPF application
val1 = textBox.Text; // Access the UI
}));
评论
关于这个问题,大多数其他答案对我来说都有点复杂(我是 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";
});
评论
首先获取窗体的实例(在本例中为 mainForm),然后在另一个线程中使用此代码。
mainForm.Invoke(new MethodInvoker(delegate ()
{
// Update things in my mainForm here
mainForm.UpdateView();
}));
关于该主题的另一个示例:我创建了一个抽象类 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
});
将一些公共变量放在一个单独的类中来保存该值。
例:
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";
}
}
最简单的方法是调用如下:
Application.Current.Dispatcher.Invoke(new Action(() =>
{
try
{
///
}
catch (Exception)
{
//
}
}
));
评论
还有另一个通用的 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;
}
);
评论
一般方法如下:
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 在创建线程上设置。InvokeRequired
control
control.SetText("some text")
control
Text
control.Text = text
System.InvalidOperationException
control.Invoke(...)
Text
control
只需使用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();
}
}
评论
上一个:随机数生成器只生成一个随机数
评论