从另一个 Thread
更新 Label
的最简单方法是什么?
我有一个在线程 1 上运行的表单,然后我正在启动另一个线程(线程 2)。
当 thread2 正在处理一些文件时,我想用 thread2 的当前工作状态更新表单上的标签。
我怎么能那样做?
最简单的方式是传递给 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
会阻止执行,直到它完成——这是同步代码。该问题没有询问异步代码,但是当您想了解异步代码时,有很多关于编写异步代码的content on 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
类的扩展方法,这样可以简化对以下内容的调用:
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 表达式来允许更简洁、更简单和更安全的语法:
myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile
现在不仅在编译时检查属性名称,而且属性的类型也是如此,因此不可能(例如)将字符串值分配给布尔属性,从而导致运行时异常。
不幸的是,这并不能阻止任何人做一些愚蠢的事情,例如传入另一个 Control
的属性和值,因此以下代码将很高兴地编译:
myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);
因此,我添加了运行时检查以确保传入的属性确实属于调用该方法的 Control
。不完美,但仍然比 .NET 2.0 版本好很多。
如果有人对如何改进此代码以提高编译时安全性有任何进一步的建议,请发表评论!
SetControlPropertyThreadSafe(myLabel, "Text", status)
可以从另一个模块或类或表单中调用吗
处理长时间的工作
由于 .NET 4.5 and C# 5.0,您应该使用 Task-based Asynchronous Pattern (TAP) 和 async-await 关键字 in all areas(包括 GUI):
TAP 是推荐用于新开发的异步设计模式
而不是 Asynchronous Programming Model (APM) 和 Event-based Asynchronous Pattern (EAP)(后者包括 BackgroundWorker Class)。
那么,新开发的推荐解决方案是:
事件处理程序的异步实现(是的,仅此而已): private async void Button_Clicked(object sender, EventArgs e) { var progress = new Progress
请注意以下事项:
以顺序方式编写的简短而干净的代码,没有回调和显式线程。任务而不是线程。 async 关键字,它允许使用 await ,它反过来阻止事件处理程序在任务完成之前达到完成状态,同时不会阻塞 UI 线程。支持关注点分离 (SoC) 设计原则且不需要显式调度程序和调用的进度类(请参阅 IProgress 接口)。它使用其创建位置(此处为 UI 线程)中的当前 SynchronizationContext。 TaskCreationOptions.LongRunning 提示不要将任务排队到 ThreadPool 中。
有关更详细的示例,请参阅:The Future of C#: Good things come to those who 'await' by Joseph Albahari。
另请参阅 UI Threading 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...");
}
}
SecondThreadConcern.LongWork()
抛出异常,是否可以被 UI 线程捕获?这是一个很好的帖子,顺便说一句。
Task.Delay(500).Wait()
?创建一个任务来阻塞当前线程有什么意义?你永远不应该阻塞线程池线程!
.NET 4 的 Marc Gravell's simplest solution 变体:
control.Invoke((MethodInvoker) (() => control.Text = "new text"));
或者改用 Action 委托:
control.Invoke(new Action(() => control.Text = "new text"));
有关两者的比较,请参见此处:MethodInvoker vs Action for Control.BeginInvoke
this.refresh()
强制使 GUI 无效并重新绘制 GUI .. 如果它有帮助..
.NET 3.5+ 的 Fire and forget 扩展方法
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
,因为它指的是调用扩展的“this Control”,并且与在普通(非扩展)代码中使用“this”关键字一致(至少在我的脑海中)。
OnUIThread
而不是 UIThread
。
RunOnUiThread
的原因。但这只是个人口味。
这是您应该执行此操作的经典方式:
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 中,你需要跨线程来改变实际的控制……比如标签或进度条。
简单的解决方案是使用 Control.Invoke
。
void DoSomething()
{
if (InvokeRequired) {
Invoke(new MethodInvoker(updateGUI));
} else {
// Do Something
updateGUI();
}
}
void updateGUI() {
// update gui here
}
线程代码通常是错误的并且总是难以测试。您无需编写线程代码即可从后台任务更新用户界面。只需使用 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
更新用户界面。
绝大多数答案使用 Control.Invoke
,即 race condition waiting to happen。例如,考虑接受的答案:
string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate {
someLabel.Text = newText; // runs on UI thread
});
如果用户在调用 this.Invoke
之前关闭表单(请记住,this
是 Form
对象),则可能会触发 ObjectDisposedException
。
解决方案是使用 SynchronizationContext
,特别是 hamilton.danielb 建议的 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's 答案以了解等效的基于任务的方法(使用 TaskScheduler.FromCurrentSynchronizationContext
)。
最后,在 .NET 4.5 及更高版本上,您还可以使用 Progress<T>
(它基本上在创建时捕获 SynchronizationContext.Current
),如 Ryszard Dżegan's 所示,用于长时间运行的操作需要在运行 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 类,它会为你抽象出这件事。
由于场景的琐碎性,我实际上会让 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.Invoke
和 ISynchronizeInvoke.BeginInvoke
方法时所需的封送操作。使用编组技术没有任何问题,但是您需要注意一些注意事项。
确保不要过于频繁地调用 BeginInvoke,否则它可能会超出消息泵。
在工作线程上调用 Invoke 是一个阻塞调用。它将暂时停止该线程中正在完成的工作。
我在这个答案中提出的策略颠倒了线程的通信角色。 UI 线程轮询数据,而不是工作线程推送数据。这是在许多场景中使用的常见模式。由于您想要做的只是显示来自工作线程的进度信息,那么我认为您会发现此解决方案是编组解决方案的绝佳替代方案。它具有以下优点。
UI 和工作线程保持松散耦合,而不是紧密耦合它们的 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;
分配它,稍后在 dispose 上下文中执行 m_Timer.Elapsed -= handler;
对吗?并按照所讨论的建议进行处置/关闭here。
您需要在 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 的解决方案,但它解决了编译时安全支持的问题。
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");
救命!在搜索了这个问题后,我发现 FrankG 和 Oregon Ghost 的答案对我来说是最简单最有用的。现在,我在 Visual Basic 中编写代码并通过转换器运行此代码段;所以我不太确定结果如何。
我有一个名为 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
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()
。
出于许多目的,它就像这样简单:
public delegate void serviceGUIDelegate();
private void updateGUI()
{
this.Invoke(new serviceGUIDelegate(serviceGUI));
}
“serviceGUI()”是表单 (this) 中的一种 GUI 级方法,可以根据需要更改任意数量的控件。从另一个线程调用“updateGUI()”。可以添加参数来传递值,或者(可能更快)使用类范围变量并根据需要对它们进行锁定,如果访问它们的线程之间存在任何可能导致不稳定的冲突的话。如果非 GUI 线程时间紧迫(记住 Brian Gideon 的警告),请使用 BeginInvoke 而不是 Invoke。
当我遇到同样的问题时,我向 Google 寻求帮助,但并没有给我一个简单的解决方案,而是给出了 MethodInvoker
和 blah blah blah 的示例,让我更加困惑。所以我决定自己解决。这是我的解决方案:
像这样做一个代表:
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();
不要与 Thread(() => .....)
混淆。我在处理线程时使用匿名函数或 lambda 表达式。为了减少代码行数,您也可以使用 ThreadStart(..)
方法,我不应该在这里解释。
这在我的 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!")
它为“as MemberExpression”的结果添加了空值检查。它提高了静态类型安全性。
否则,原版是一个非常好的解决方案。
只需使用这样的东西:
this.Invoke((MethodInvoker)delegate
{
progressBar1.Value = e.ProgressPercentage; // runs on UI thread
});
e.ProgressPercentage
,您是不是已经在您调用它的方法的 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";
});
我的版本是插入一行递归“咒语”:
对于没有参数:
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}
,您只知道“从另一个线程调用此函数是安全的”。
您可以使用已经存在的委托 Action
:
private void UpdateMethod()
{
if (InvokeRequired)
{
Invoke(new Action(UpdateMethod));
}
}
创建一个类变量:
SynchronizationContext _context;
在创建 UI 的构造函数中设置它:
var _context = SynchronizationContext.Current;
当您要更新标签时:
_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;
}
);
您必须使用调用和委托
private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
尝试使用此刷新标签
public static class ExtensionMethods
{
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement)
{
uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
}
}
WPF 应用程序中最简单的方法是:
this.Dispatcher.Invoke((Action)(() =>
{
// This refers to a form in a WPF application
val1 = textBox.Text; // Access the UI
}));
当您在 UI 线程中时,您可以向它询问其同步上下文任务调度程序。它会为您提供一个在 UI 线程上安排所有内容的 TaskScheduler。
然后,您可以链接您的任务,以便当结果准备好时,另一个任务(在 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;
}
}
这适用于 preferred way of writing concurrent code now 的任务(不是线程)。
Task.Start
通常不是一个好习惯blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspx
例如,访问当前线程以外的控件:
Speed_Threshold = 30;
textOutput.Invoke(new EventHandler(delegate
{
lblThreshold.Text = Speed_Threshold.ToString();
}));
lblThreshold
是一个标签,Speed_Threshold
是一个全局变量。
不定期副业成功案例分享