C# 如何从另一个线程更新GUI?
从另一个线程更新标签的最简单方法是什么 我在thread1上运行了一个表单,然后我开始了另一个thread2线程 当thread2正在处理一些文件时,我想用thread2工作的当前状态更新表单上的标签C# 如何从另一个线程更新GUI?,c#,.net,multithreading,winforms,user-interface,C#,.net,Multithreading,Winforms,User Interface,从另一个线程更新标签的最简单方法是什么 我在thread1上运行了一个表单,然后我开始了另一个thread2线程 当thread2正在处理一些文件时,我想用thread2工作的当前状态更新表单上的标签 我该怎么做呢?您必须确保更新发生在正确的线程上;用户界面线程 为了做到这一点,您必须调用事件处理程序,而不是直接调用它 您可以通过以下方式提高您的活动: // thread-safe equivalent of // myLabel.Text = status; SetControlPropert
我该怎么做呢?您必须确保更新发生在正确的线程上;用户界面线程 为了做到这一点,您必须调用事件处理程序,而不是直接调用它 您可以通过以下方式提高您的活动:
// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);
myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
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
}
this.Invoke((MethodInvoker)delegate
{
progressBar1.Value = e.ProgressPercentage; // runs on UI thread
});
这段代码是我脑子里想出来的,所以我还没有检查语法是否正确,等等,但它应该可以让你继续
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
您需要在GUI线程上调用该方法。您可以通过调用Control.Invoke来实现这一点 例如:
delegate void UpdateLabelDelegate (string message);
void UpdateLabel (string message)
{
if (InvokeRequired)
{
Invoke (new UpdateLabelDelegate (UpdateLabel), message);
return;
}
MyLabelControl.Text = message;
}
最简单的方法是将匿名方法传递到:
请注意,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,下面是我编写的一段代码,它完全符合您的要求,适用于控件上的任何属性: 可以这样称呼:
// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);
myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
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
}
this.Invoke((MethodInvoker)delegate
{
progressBar1.Value = e.ProgressPercentage; // runs on UI thread
});
如果您使用的是.NET 3.0或更高版本,则可以将上述方法重写为控件类的扩展方法,从而简化对以下对象的调用:
myLabel.SetPropertyThreadSafe("Text", status);
2010年10月5日更新:
对于.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 });
}
}
现在不仅在编译时检查属性名称,属性的类型也是如此,因此不可能例如为布尔属性指定字符串值,从而导致运行时异常
不幸的是,这并不能阻止任何人做愚蠢的事情,例如传入另一个控件的属性和值,因此下面的代码将很好地编译:
myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);
因此,我添加了运行时检查,以确保传入的属性确实属于调用该方法的控件。虽然不完美,但仍比.NET2.0版本好很多
如果有人对如何改进此代码以提高编译时安全性有任何进一步的建议,请发表评论 出于许多目的,它就这么简单:
public delegate void serviceGUIDelegate();
private void updateGUI()
{
this.Invoke(new serviceGUIDelegate(serviceGUI));
}
serviceGUI是表单中的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...";
}
}
}
该方法避免了使用ISynchronizeInvoke.Invoke和ISynchronizeInvoke.BeginInvoke方法时所需的封送处理操作。使用封送处理技术没有什么错,但有几个注意事项需要注意
请确保不要过于频繁地呼叫BeginInvoke,否则可能会导致消息溢出。
对工作线程调用Invoke是一个阻塞调用。它将暂时停止该线程中正在进行的工作。
我在这个答案中提出的策略颠倒了线程的通信角色。UI线程轮询数据,而不是工作线程推送数据。这是许多场景中使用的常见模式。由于您只想显示工作线程的进度信息,因此我认为您会发现此解决方案是封送解决方案的一个很好的替代方案。它有以下优点
UI和工作线程保持松散耦合,而不是采用将它们紧密耦合的Control.Invoke或Control.BeginInvoke方法。
UI线程不会妨碍程序
工作线程的ess。
工作线程不能支配UI线程花在更新上的时间。
UI和工作线程执行操作的间隔可以保持独立。
工作线程不能超出UI线程的消息泵。
UI线程可以指定UI更新的时间和频率。
线程代码通常有缺陷,并且总是很难测试。从后台任务更新用户界面不需要编写线程代码。只需使用类来运行任务,并使用它的方法来更新用户界面。通常,您只报告一个完成百分比,但还有另一个重载包含一个状态对象。下面是一个仅报告字符串对象的示例:
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方法将被完全忽略。为.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();
}
}
}
这个解决方案类似于上面使用.NETFramework 3.0的解决方案,但它解决了编译时安全支持的问题 如果用户传递错误的数据类型,编译器将失败
this.lblTimeDisplay.SetPropertyValue(a => a.Visible, "sometext");
这是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);
}
它将null检查添加到as MemberExpression的结果中。
它提高了静态类型的安全性。
否则,原始版本是一个非常好的解决方案。Salvete!在寻找这个问题之后,我发现弗兰克和俄勒冈州幽灵的答案对我来说是最简单最有用的。现在,我用Visual Basic编写代码,并通过转换器运行此代码段;所以我不确定结果如何 我有一个名为form_Diagnostics的对话框,它有一个名为updateDiagWindow的richtext框,我使用它作为一种日志显示。我需要能够从所有线程更新它的文本。额外的行允许窗口自动滚动到最新的行 因此,我现在可以用一行更新显示,从整个程序的任何地方,以您认为的方式,在没有任何线程的情况下工作:
form_Diagnostics.updateDiagWindow(whatmessage);
主代码将此项放在窗体的类代码中:
我的版本是插入一行递归咒语:
对于无参数:
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!
}
对于具有参数的函数:
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语句的一行之后对代码可读性不利。但在这种情况下,它是例行的所有相同的咒语。如果此方法在整个项目中保持一致,则不会破坏代码的可读性。它还可以避免您的代码乱扔一行代码而不是五行代码
正如您所看到的,ifInvokeRequired{something long}您只知道从另一个线程调用这个函数是安全的
Label lblText; //initialized elsewhere
void AssignLabel(string text)
{
if (InvokeRequired)
{
BeginInvoke((Action<string>)AssignLabel, text);
return;
}
lblText.Text = text;
}
请注意,BeginInvoke优先于Invoke,因为它不太可能导致死锁。但是,当仅将文本分配给标签时,这不是一个问题:
使用Invoke时,您正在等待方法返回。现在,可能是您在调用的代码中执行了一些需要等待线程的操作,如果线程被隐藏在您正在调用的某些函数中,这可能不会立即显现出来,而这些函数本身可能通过事件处理程序间接发生。因此,您将等待线程,线程将等待您,而您处于死锁状态
这实际上导致我们发布的一些软件挂起。用BeginInvoke替换Invoke很容易修复。除非您需要同步操作(如果您需要返回值,则可能需要同步操作),否则请使用BeginInvoke。for.NET 4的变体:
control.Invoke((MethodInvoker) (() => control.Text = "new text"));
或者改用动作委托:
control.Invoke(new Action(() => control.Text = "new text"));
请参见此处以了解两者的比较:您可以使用现有的委托操作:
当我遇到同样的问题时,我向谷歌寻求帮助,但没有给我一个简单的解决方案,而是举了MethodInvoker和诸如此类的例子,让我更加困惑。所以我决定自己解决这个问题。以下是我的解决方案:
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);
}
使委托如下所示:
// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);
myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
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
}
this.Invoke((MethodInvoker)delegate
{
progressBar1.Value = e.ProgressPercentage; // runs on UI thread
});
您可以在这样的新线程中调用此函数
Thread th = new Thread(() => Updatelabel("Hello World"));
th.start();
object1.InvokeIfRequired(c =>
{
c.Text = "ABC";
c.Visible = true;
}
);
不要与Thread=>……混淆。。。。。。在线程上工作时,我使用匿名函数或lambda表达式。要减少代码行数,可以使用ThreadStart。。方法,我不想在这里解释。前面答案中的调用内容都不是必需的 您需要查看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);
}
}
处理长期工作
由于您应该与-关键字一起使用,包括GUI:
TAP是新开发推荐的异步设计模式
而后者包括
那么,新开发的建议解决方案是:
事件处理程序的异步实现是的,仅此而已:
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";
}
必须使用invoke和delegate
private delegate void MyLabelDelegate();
label1.Invoke( new MyLabelDelegate(){ label1.Text += 1; });
创建类变量:
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();
}));
lblThreshold是一个标签,Speed_Threshold是一个全局变量。当您在UI线程中时,可以向它请求其同步上下文任务调度器。它将为您提供一个调度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;
}
}
这适用于任务,而不是线程。绝大多数答案都使用Control.Invoke,这是一种。例如,考虑公认的答案:
string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate {
someLabel.Text = newText; // runs on UI thread
});
如果用户在调用this.Invoke之前关闭表单,请记住,这是表单对象,可能会触发ObjectDisposedException
解决方案是使用SynchronizationContext,特别是SynchronizationContext。当前的其他答案依赖于特定的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);
}
}
请注意,在.NET4.0及更高版本上,您应该真正将任务用于异步操作。请参阅使用TaskScheduler.FromCurrentSynchronizationContext的等效基于任务的方法的答案
最后,在.NET 4.5及更高版本上,您还可以使用Progress,它基本上捕获SynchronizationContext.Current(同步上下文创建时的当前状态),如所示,对于长时间运行的操作需要在仍然工作的情况下运行UI代码的情况。只需使用以下内容:
// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);
myButton.SetPropertyInGuiThread(b => b.Text, "Click Me!")
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
}
this.Invoke((MethodInvoker)delegate
{
progressBar1.Value = e.ProgressPercentage; // runs on UI thread
});
WPF应用程序中最简单的方法是:
this.Dispatcher.Invoke((Action)(() =>
{
// This refers to a form in a WPF application
val1 = textBox.Text; // Access the UI
}));
对于这个问题,大多数其他答案对我来说都有点复杂。我对C语言还不熟悉,所以我写了我的: 我有一个WPF应用程序,并定义了一个worker,如下所示: 问题:
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"));
}
txtLog.Invoke((MethodInvoker)delegate
{
txtLog.Text = "my message";
});
我还没有弄清楚上面这句话的意思,但它是有效的
对于WinForms:
解决方案:
workerAllocator.DoWork += delegate (object sender1, DoWorkEventArgs e1)
{
// The below single line works
txtLog.Dispatcher.BeginInvoke((Action)(() => txtLog.Text = "my message"));
}
txtLog.Invoke((MethodInvoker)delegate
{
txtLog.Text = "my message";
});
还有另一个通用控件扩展aproach
首先为Control类型的对象添加扩展方法
…或者像这样
Thread th = new Thread(() => Updatelabel("Hello World"));
th.start();
object1.InvokeIfRequired(c =>
{
c.Text = "ABC";
c.Visible = true;
}
);
的确如此,但我不想因为这件事而“弄乱”我的GUI代码。我的GUI不应该关心是否需要调用。换句话说:我不认为GUI有责任执行上下文转换。将委托分离等似乎有些过分-为什么不只是:SynchronizationContext.Current.Senddelegate{MyEvent…;},null;您是否始终可以访问SynchronizationContext?即使您的类在类库中?因为OP没有提到除表单之外的任何类/实例,所以这是一个不错的默认值…不要忘记this关键字正在引用控件类。@code完成这两种方式都是安全的,而且我们已经知道我们使用的是worker,那么,为什么要检查我们知道的东西呢?@Dragouf实际上不是这样-使用此方法的一个要点是,您已经知道哪些部分在worker上运行,哪些部分在UI线程上运行。不需要检查。@John,因为Control.Invoke对任何委托都是这样做的-不仅仅是一个非方法调用行给了我一个编译器错误。与“System.Windows.Forms.Control.InvokeSystem.Delegate,object[]”匹配的最佳重载方法具有一些无效参数。有时,this.GetType的计算结果与propertyInfo.ReflectedType相同,例如WinForms上的LinkLabel。我没有大型C语言的经验,但我认为异常的条件应该是:if propertyInfo==null | |@this.GetType.IsSubclassOfpropertyInfo.ReflectedType&&@this.GetType!=propertyInfo.ReflectedType | |@this.GetType.GetPropertypropertyInfo.Name,propertyInfo.PropertyType==null@lan是否可以从其他模块或类或表单调用此SetControlPropertyThreadSafeMailLabel、Text、status?提供的解决方案过于复杂。如果您重视简单性,请参阅Marc Gravell的解决方案或Zaid Masud的解决方案。如果您更新多个属性,此解决方案会浪费大量资源,因为每次调用都会耗费大量资源。我不认为这是线程安全特性的初衷。请封装您的UI更新操作并调用它一次,而不是每个属性。您究竟会在BackgroundWorker组件上使用此代码吗?难道.net 2.0+没有BackgroundWorker类仅用于此目的吗。它支持UI线程感知。1.创建一个BackgroundWorker 2。添加两个代理—一个用于处理,一个用于完成—可能有点晚:请参见.NET 4.5和C 5.0的答案:此问题不适用于Gtk GUI。对于Gtk,请参阅并回答。注意:这个问题的答案现在是一堆杂乱无章的OT。以下是我为我的WPF应用程序和历史.NET 2.0工件所做的操作。@this用法的意义是什么?控制难道不是等价的吗?有本吗
efits to@this?@jeromeyers,-@this只是变量名,在本例中是对调用扩展名的当前控件的引用。你可以把它改名为source,或者任何能让你的船漂浮的东西。我使用@this,因为它指的是调用扩展的“this Control”,在我的脑海中是一致的,至少在正常的非扩展代码中使用了“this”关键字。这很好,很简单,对我来说是最好的解决方案。您可以在ui线程中包含所有必须完成的工作。示例:this.UIThread=>{txtMessage.Text=message;listBox1.Items.Addmessage;};我真的很喜欢这个解决方案。次要缺点:我想把这个方法命名为OnUIThread而不是UIThread。这就是为什么我把这个扩展命名为rununuithread。但这只是个人品味。好主意。您唯一没有提到的是如何在WorkerThread完成后正确处理计时器。注意:当应用程序结束时,即用户关闭应用程序时,这可能会导致问题。您知道如何解决此问题吗?@Matt您使用成员方法,而不是使用已用事件的匿名处理程序,以便在释放表单时删除计时器…@Phil1970-这一点很好。您的意思是像System.Timers.ElapsedEventHandler handler=s,a=>{MyProgressLabel.Text=m_Text;};并通过m_Timer.appeased+=handler;分配它;,稍后在dispose上下文中执行m_Timer.appeased-=处理程序;我说得对吗?如果SecondThreadConcern.LongWork引发异常,则调用Task.Start通常不是一个好的做法,UI线程是否可以捕获该异常?顺便说一句,这是一篇很好的帖子。我在答案中增加了一个部分来满足你的要求。注意。在异步等待模式中,用户界面线程上的后台异常被重新触发,这是不是因为我认为这种方式比调用Invoke/Begin更冗长?!Task.Delay500.等等?创建一个任务来阻止当前线程有什么意义?永远不要阻止线程池线程!做得好,简单!不仅简单,而且效果很好!我真的不明白为什么微软不能让它变得更简单,因为它是注定的!为了在主线程上调用一行,我们应该编写两个函数@MBH同意。顺便说一句,你们注意到上面的答案了吗?它定义了一个扩展方法?在自定义实用程序类中这样做一次,就不必再关心微软没有为我们这样做:@ToolmakerSteve这正是它的本意!你是对的,我们可以找到一种方法,但我的意思是,从DRY的观点来看,有共同解决方案的问题,可以由微软以最小的努力解决,这将为程序员节省大量时间:如果你有e.ProgressPercentage,您不是已经从调用此方法的方法进入UI线程了吗?ProgressChanged事件在UI线程上运行。这是使用BackgroundWorker的便利之一。完成的事件也在gui上运行。非UI线程中唯一运行的是DoWork方法。在处理结束时,还可以通过backgroundWorker1\u RunWorkerCompleted更新用户界面。您认为Post方法在幕后使用什么如果您使用的是WPF应用程序,则这是正确的。但是他使用的是Windows窗体。你甚至可以在Winforms应用程序中使用Dispatcher。本例中的“控制”是什么?我的UI控件?试图在label控件的WPF中实现此功能,而Invoke不是我的标签的成员。像@styxriver这样的扩展方法如何?声明“Action y;”在类或方法内部更改文本属性并使用以下代码段更新文本“yourcontrol.Invokey==>yourcontrol.text=newtext;”@Dbloom它不是成员,因为它仅用于WinForms。对于WPF,您使用Dispatcher.InvokeI遵循此解决方案,但有时我的UI没有得到更新。我发现我需要这个.refresh来强制失效并重新绘制GUI。。如果有帮助的话..是针对Windows窗体的吗?问题是关于Winforms,而不是WPF。谢谢。上面添加了WinForms解决方案…这只是关于同一问题的许多其他答案的副本,但还可以。为什么不成为解决方案的一部分并删除你的答案呢?嗯,你是正确的,如果你专心阅读我的答案,开头部分就是我写答案的原因,希望你能更专心一点,看到有人有完全相同的问题&今天对我的简单答案投了赞成票,如果你能预见到这一切发生的真实原因,那就是谷歌甚至在我搜索wpf的时候把我送到这里。当然,既然你错过了这三个或多或少明显的原因,我可以理解你为什么不取消你的否决票。而不是清洁好的,创造一些新的,这是非常困难的。非常优雅,非常好!我已经开始你了 使用c.BeginInvoke进行异步更新。如果在级联中调用,则不太可能导致死锁。