Label
从另一个更新a的最简单方法是Thread
什么?
我
Form
正在上运行thread1
,然后从中启动另一个线程(thread2
)。在
thread2
处理某些文件时,我想用的工作状态更新Label
上的。Form
thread2
我该怎么办?
Label
从另一个更新a的最简单方法是Thread
什么?
我Form
正在上运行thread1
,然后从中启动另一个线程(thread2
)。
在thread2
处理某些文件时,我想用的工作状态更新Label
上的。Form
thread2
我该怎么办?
Answers:
对于.NET 2.0,这是我编写的大量代码,完全可以实现您想要的功能,并且可以在a上的任何属性下使用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)
从另一个模块或类或表单中调用@lan
在最简单的方法是匿名方法传递到Label.Invoke
:
// Running on the worker thread
string newText = "abc";
form.Label.Invoke((MethodInvoker)delegate {
// Running on the UI thread
form.Label.Text = newText;
});
// Back on the worker thread
请注意,Invoke
直到执行完成为止,它都会阻塞执行-这是同步代码。这个问题并没有询问异步代码,但是您想了解异步代码时,Stack Overflow上有很多内容涉及编写异步代码。
从.NET 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());
}
}
}
请注意以下几点:
有关更详细的示例,请参见:C#的未来:约瑟夫·阿尔巴哈里(Joseph Albahari )“等待”的人们会感到高兴。
另请参阅关于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()
抛出异常,UI线程是否可以捕获它?顺便说一句,这是一个很棒的帖子。
Task.Delay(500).Wait()
?创建仅阻止当前线程的任务有什么意义?您永远不应阻塞线程池线程!
Marc Gravell针对.NET 4 的最简单解决方案的变体:
control.Invoke((MethodInvoker) (() => control.Text = "new text"));
或改用Action委托:
control.Invoke(new Action(() => control.Text = "new text"));
参见此处,对两者进行比较:MethodInvoker与Action for Control.BeginInvoke
this.refresh()
强制无效并重新绘制GUI ..如果有帮助的话..
.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
,因为它是指正在调用扩展的“ 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线程从另一个线程开始执行工作,并连接该工作程序事件,以便您可以显示工作程序线程的状态。
然后,在用户界面中,您需要跨线程来更改实际控件……例如标签或进度条。
简单的解决方案是使用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
,这是等待发生的竞赛条件。例如,考虑接受的答案:
string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate {
someLabel.Text = newText; // runs on UI thread
});
如果用户在this.Invoke
调用之前关闭表单(记住,this
是Form
对象),ObjectDisposedException
则很可能会触发。
解决方案是使用SynchronizationContext
,具体SynchronizationContext.Current
如hamilton.danielb所建议的那样(其他答案取决于SynchronizationContext
完全没有必要的特定实现)。我会稍微修改他的代码以使用,SynchronizationContext.Post
而不是使用SynchronizationContext.Send
(因为通常不需要工作线程等待):
public partial class MyForm : Form
{
private readonly SynchronizationContext _context;
public MyForm()
{
_context = SynchronizationContext.Current
...
}
private MethodOnOtherThread()
{
...
_context.Post(status => someLabel.Text = newText,null);
}
}
请注意,在.NET 4.0及更高版本上,您实际上应该将任务用于异步操作。请参见n-san的答案,以了解基于任务的等效方法(使用TaskScheduler.FromCurrentSynchronizationContext
)。
最后,在Ryszard D onegan的演示中,在.NET 4.5及更高版本上,您还可以使用Progress<T>
(基本上会SynchronizationContext.Current
在创建时捕获)在长时间运行的操作仍需要运行UI代码的情况下。
您必须确保更新发生在正确的线程上。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控件未实现该ISynchronizeInvoke
接口,因此上面的代码不适用于WPF项目。
为了确保以上Windows窗体和WPF,和所有其他平台作品的代码,你可以看看AsyncOperation
,AsyncOperationManager
和SynchronizationContext
类。
为了以这种方式轻松引发事件,我创建了一个扩展方法,该方法允许我通过调用以下方法简化引发事件:
MyEvent.Raise(this, EventArgs.Empty);
当然,您也可以使用BackGroundWorker类,该类将为您抽象此问题。
您需要在GUI线程上调用该方法。您可以通过调用Control.Invoke来实现。
例如:
delegate void UpdateLabelDelegate (string message);
void UpdateLabel (string message)
{
if (InvokeRequired)
{
Invoke (new UpdateLabelDelegate (UpdateLabel), message);
return;
}
MyLabelControl.Text = message;
}
由于场景的琐碎性,我实际上将获得状态的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线程会轮询它,而不是辅助线程推送数据。这是在许多情况下使用的常见模式。因为您要做的只是显示工作线程中的进度信息,所以我认为您会发现该解决方案是封送处理解决方案的绝佳替代方案。具有以下优点。
Control.Invoke
or Control.BeginInvoke
方法。Elapsed
使用成员方法,而不是使用匿名的事件处理程序,因此可以在处理表单时删除计时器...
先前答案中的“调用”东西都不是必需的。
您需要查看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");
Salvete!搜索了这个问题之后,我发现FrankG和Oregon Ghost的答案对我来说是最简单,最有用的。现在,我用Visual Basic编写代码,并通过转换器运行此代码段。所以我不确定结果如何。
我有一个对话框窗体form_Diagnostics,
,其中有一个RTF框,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
出于许多目的,它就像这样简单:
public delegate void serviceGUIDelegate();
private void updateGUI()
{
this.Invoke(new serviceGUIDelegate(serviceGUI));
}
“ serviceGUI()”是表单(this)中的GUI级别方法,可以根据需要更改任意数量的控件。从另一个线程调用“ updateGUI()”。可以添加参数以传递值,或者(如果更快)在访问它们的线程之间可能发生冲突而导致不稳定的情况下,根据需要使用具有锁的类范围变量。如果非GUI线程对时间很紧迫,请使用BeginInvoke而不是Invoke(牢记Brian Gideon的警告)。
这是我的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!")
否则,原始版本是一个非常好的解决方案。
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()
。
当我遇到相同的问题时,我寻求Google的帮助,但没有给我简单的解决方案,反而给我更多的例子,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();
不要与混淆Thread(() => .....)
。在线程上工作时,我使用匿名函数或lambda表达式。为了减少代码行,您也可以使用在ThreadStart(..)
此不做解释的方法。
您可以使用已经存在的委托Action
:
private void UpdateMethod()
{
if (InvokeRequired)
{
Invoke(new Action(UpdateMethod));
}
}
我的版本是插入一行递归“咒语”:
没有参数:
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}
您只知道“可以从另一个线程安全地调用此函数”。
尝试使用此刷新标签
public static class ExtensionMethods
{
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement)
{
uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
}
}
在这个问题上,大多数其他答案对我来说有点复杂(我是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 Update()
{
BeginInvoke((Action)delegate()
{
//do your update
});
}
当您进入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
通常不是一个好习惯blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspx
我刚刚阅读了答案,这似乎是一个非常热门的话题。我目前正在使用.NET 3.5 SP1和Windows窗体。
在前面的答案中大大描述了一个众所周知的公式,该公式利用了InvokeRequired属性,它涵盖了大多数情况,但没有涵盖整个情况。
如果尚未创建句柄怎么办?
如果调用是从不是GUI线程的线程进行的,则此处描述的InvokeRequired属性(对MSDN的Control.InvokeRequired属性的引用)将返回true;如果调用是从GUI线程进行的,或者如果Handle是尚未创建。
如果您想让模式窗体显示并由另一个线程更新,则可能会遇到异常。因为您希望该表格以模态显示,所以您可以执行以下操作:
private MyForm _gui;
public void StartToDoThings()
{
_gui = new MyForm();
Thread thread = new Thread(SomeDelegate);
thread.Start();
_gui.ShowDialog();
}
委托可以在GUI上更新Label:
private void SomeDelegate()
{
// Operations that can take a variable amount of time, even no time
//... then you update the GUI
if(_gui.InvokeRequired)
_gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
else
_gui.Label1.Text = "Done!";
}
如果标签更新之前的操作“花费更少的时间”(读取并解释为简化)比GUI线程创建Form的Handle花费的时间少,则可能导致InvalidOperationException。这发生在ShowDialog()方法中。
您还应该像这样检查句柄:
private void SomeDelegate()
{
// Operations that can take a variable amount of time, even no time
//... then you update the GUI
if(_gui.IsHandleCreated) // <---- ADDED
if(_gui.InvokeRequired)
_gui.Invoke((Action)delegate { _gui.Label1.Text = "Done!"; });
else
_gui.Label1.Text = "Done!";
}
如果尚未创建Handle,则可以处理要执行的操作:您可以忽略GUI更新(如上面的代码所示),也可以等待(风险更大)。这应该回答这个问题。
可选内容:我个人想出了以下代码:
public class ThreadSafeGuiCommand
{
private const int SLEEPING_STEP = 100;
private readonly int _totalTimeout;
private int _timeout;
public ThreadSafeGuiCommand(int totalTimeout)
{
_totalTimeout = totalTimeout;
}
public void Execute(Form form, Action guiCommand)
{
_timeout = _totalTimeout;
while (!form.IsHandleCreated)
{
if (_timeout <= 0) return;
Thread.Sleep(SLEEPING_STEP);
_timeout -= SLEEPING_STEP;
}
if (form.InvokeRequired)
form.Invoke(guiCommand);
else
guiCommand();
}
}
我使用此ThreadSafeGuiCommand的实例来提供由另一个线程更新的表单,并定义如下方法来更新GUI(在我的Form中):
public void SetLabeTextTo(string value)
{
_threadSafeGuiCommand.Execute(this, delegate { Label1.Text = value; });
}
通过这种方式,我非常确定我将使GUI进行更新,无论发出呼叫的线程是什么,可以选择等待明确定义的时间(超时)。