从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
从另一个线程更新标签的最简单方法是什么?
我在thread1上运行了一个Form,然后我开始另一个线程(thread2)。当thread2处理一些文件时,我想用thread2工作的当前状态更新表单上的标签。
我怎么能这样做?
当前回答
处理长工作
由于.NET 4.5和C#5.0,您应该在所有领域(包括GUI)使用基于任务的异步模式(TAP)和异步等待关键字:
TAP是新开发推荐的异步设计模式
而不是异步编程模型(APM)和基于事件的异步模式(EAP)(后者包括BackgroundWorker类)。
那么,新开发的建议解决方案是:
事件处理程序的异步实现(是的,仅此而已):私有异步void Button_Clicked(对象发送方,EventArgs e){var progress=new progress<string>(s=>label.Text=s);await Task.Factory.StartNew(()=>SecondThreadConcern.LongWork(进度),TaskCreationOptions.LongRunning);label.Text=“已完成”;}通知UI线程的第二个线程的实现:类SecondThreadConcern{公共静态void LongWork(IProgress<string>进度){//进行长时间的工作。。。对于(变量i=0;i<10;i++){任务.延迟(500).等待();progress.Report(i.ToString());}}}
请注意以下事项:
以顺序方式编写的简短而干净的代码,没有回调和显式线程。任务而不是线程。async关键字,它允许使用await,从而防止事件处理程序在任务完成之前达到完成状态,同时不会阻塞UI线程。进程类(参见IProgress接口),支持关注点分离(SoC)设计原则,不需要显式调度程序和调用。它使用创建位置(此处为UI线程)的当前SynchronizationContext。TaskCreationOptions.LongRunning,提示不要将任务排入ThreadPool。
有关更详细的示例,请参阅约瑟夫·阿尔巴哈里(Joseph Albahari)的《C#的未来:美好的事物降临在那些“等待”的人身上》。
另请参见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...");
}
}
其他回答
当您在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;
}
}
这适用于任务(而不是线程),这是目前编写并发代码的首选方式。
致敬!在搜索了这个问题之后,我发现FrankG和Oregon Ghost的答案对我来说是最简单、最有用的;所以我不太确定结果如何。
我有一个名为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
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
前面回答中的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
}
也许有点过量,但这是我通常解决问题的方式:
由于同步,此处不需要调用。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); }
}