从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用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类,它将为您抽象这个问题。
其他回答
您必须确保更新发生在正确的线程上;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类,它将为您抽象这个问题。
关于这个主题的另一个示例:我创建了一个抽象类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";
}
}
只使用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();
}
}
绝大多数答案使用Control.Invoke,这是等待发生的竞争条件。例如,考虑接受的答案:
string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate {
someLabel.Text = newText; // runs on UI thread
});
如果用户在调用this.Invoke之前关闭表单(请记住,这是form对象),则可能会触发ObjectDisposedException。
解决方案是使用SynchronizationContext,特别是hamilton.danielb建议的SynchronizaionContext.Current(其他答案依赖于特定的Synchronization Context实现,这是完全不必要的)。我会稍微修改他的代码,使用SynchronizationContext.Post而不是SynchronizazationContext.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),请参见n-san的答案。
最后,在.NET 4.5及更高版本上,您还可以使用Progress<T>(它基本上在创建时捕获SynchronizationContext.Current),正如Ryszard Dżegan所演示的,用于长时间运行的操作需要在运行时运行UI代码的情况。