如何用backgroundworker更新GUI?
我花了整整一天的时间试图让我的应用程序使用线程,但没有运气。 我已经阅读了很多关于它的文档,但是我仍然有很多错误,所以我希望你能帮助我。
我有一个很耗时的方法调用数据库并更新GUI。 这一直发生(或大约每30秒)。
public class UpdateController { private UserController _userController; public UpdateController(LoginController loginController, UserController userController) { _userController = userController; loginController.LoginEvent += Update; } public void Update() { BackgroundWorker backgroundWorker = new BackgroundWorker(); while(true) { backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork); backgroundWorker.RunWorkerAsync(); } } public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) { _userController.UpdateUsersOnMap(); } }
使用这种方法,我得到一个exception,因为背景工作不是和STA线程(但从我能理解这是我应该使用)。 我已经尝试了一个STA线程,并给了其他错误。
我认为这个问题是因为我尝试在进行数据库调用时更新GUI(在后台线程中)。 我应该只做数据库调用,然后以某种方式切换回主线程。 主线程执行后,应该回到后台线程等等。 但是我不明白怎么做。
数据库调用后,应用程序应该立即更新GUI。 消防活动似乎并不奏效。 backgroundthread只是input他们。
编辑:
一些非常好的答案:)这是新的代码:
public class UpdateController{ private UserController _userController; private BackgroundWorker _backgroundWorker; public UpdateController(LoginController loginController, UserController userController) { _userController = userController; loginController.LoginEvent += Update; _backgroundWorker = new BackgroundWorker(); _backgroundWorker.DoWork += backgroundWorker_DoWork; _backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted; } public void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { _userController.UpdateUsersOnMap(); } public void Update() { _backgroundWorker.RunWorkerAsync(); } void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { //UI update System.Threading.Thread.Sleep(10000); Update(); } public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) { // Big database task }
}
但是我怎样才能让这个每10秒钟运行? System.Threading.Thread.Sleep(10000)将使我的graphics用户界面冻结,while(true)在Update()中循环build议给出一个exception(线程太忙)。
您需要声明和configuration一次BackgroundWorker – 然后在您的循环中调用RunWorkerAsync方法…
public class UpdateController { private UserController _userController; private BackgroundWorker _backgroundWorker; public UpdateController(LoginController loginController, UserController userController) { _userController = userController; loginController.LoginEvent += Update; _backgroundWorker = new BackgroundWorker(); _backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork); _backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged); _backgroundWorker.WorkerReportsProgress= true; } public void Update() { _backgroundWorker.RunWorkerAsync(); } public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) { while (true) { // Do the long-duration work here, and optionally // send the update back to the UI thread... int p = 0;// set your progress if appropriate object param = "something"; // use this to pass any additional parameter back to the UI _backgroundWorker.ReportProgress(p, param); } } // This event handler updates the UI private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { // Update the UI here // _userController.UpdateUsersOnMap(); } }
您必须使用Control.InvokeRequired属性来确定您是否在后台线程上。 然后你需要通过Control.Invoke方法调用你的修改你的UI的逻辑来强制你的UI操作在主线程上发生。 您可以通过创build委托并将其传递给Control.Invoke方法来完成此操作。 这里的catch是你需要一些来自Control的对象来调用这些方法。
编辑 :另一个用户发布,如果你可以等待BackgroundWorker.Completed事件来更新你的用户界面,那么你可以订阅该事件,并直接调用你的UI代码。 BackgroundWorker_Completed在主应用程序线程上调用。 我的代码假设你想在操作过程中做更新。 我的方法的另一种方法是订阅BwackgroundWorker.ProgressChanged事件,但是我相信在这种情况下,您仍然需要调用Invoke来更新您的UI。
例如
public class UpdateController { private UserController _userController; BackgroundWorker backgroundWorker = new BackgroundWorker(); public UpdateController(LoginController loginController, UserController userController) { _userController = userController; loginController.LoginEvent += Update; } public void Update() { // The while loop was unecessary here backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork); backgroundWorker.RunWorkerAsync(); } public delegate void DoUIWorkHandler(); public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) { // You must check here if your are executing on a background thread. // UI operations are only allowed on the main application thread if (someControlOnMyForm.InvokeRequired) { // This is how you force your logic to be called on the main // application thread someControlOnMyForm.Invoke(new DoUIWorkHandler(_userController.UpdateUsersOnMap); } else { _userController.UpdateUsersOnMap() } } }
你应该删除while(真),你正在添加无限的事件处理程序,并调用它们无限的时间。
您可以使用backgroundWorker类上的RunWorkerCompleted事件来定义后台任务完成后应执行的操作。 所以你应该在DoWork处理程序中执行数据库调用,然后在RunWorkerCompleted处理程序中更新接口,如下所示:
BackgroundWorker bgw = new BackgroundWorker(); bgw.DoWork += (o, e) => { longRunningTask(); } bgw.RunWorkerCompleted += (o, e) => { if(e.Error == null && !e.Cancelled) { _userController.UpdateUsersOnMap(); } } bgw.RunWorkerAsync();
除了以前的评论,看看www.albahari.com/threading – 线程最好的文档,你会发现。 它会教你如何正确使用BackgroundWorker。
当BackgroundWorker触发Completed事件(在UI线程上调用该事件以使其更容易,从而不必自己执行Control.Invoke)时,应该更新GUI。
@李的回答中的if语句应该如下所示:
bgw.RunWorkerCompleted += (o, e) => { if(e.Error == null && !e.Cancelled) { _userController.UpdateUsersOnMap(); } }
…如果你想调用UpdateUsersOnMap();
当没有错误和BgWorker尚未取消。
这里有一个你可以使用的源代码模式。 在这个例子中,我正在redirect控制台,然后我使用这个控制台让后台工作人员在处理文本时将一些消息写入文本框。
这是帮助器类TextBoxStreamWriter,它用于redirect控制台输出:
public class TextBoxStreamWriter : TextWriter { TextBox _output = null; public TextBoxStreamWriter(TextBox output) { _output = output; } public override void WriteLine(string value) { // When character data is written, append it to the text box. // using Invoke so it works in a different thread as well _output.Invoke((Action)(() => _output.AppendText(value+"\r\n"))); } }
您需要在表单加载事件中使用它,如下所示:
private void Form1_Load(object sender, EventArgs e) { // Instantiate the writer and redirect the console out var _writer = new TextBoxStreamWriter(txtResult); Console.SetOut(_writer); }
在启动后台工作的表单上还有一个button,它将一个path传递给它:
private void btnStart_Click(object sender, EventArgs e) { backgroundWorker1.RunWorkerAsync(txtPath.Text); }
这是后台工作负载,请注意如何使用控制台将消息输出到文本框:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { var selectedPath = e.Argument as string; Console.Out.WriteLine("Processing Path:"+selectedPath); // ... }
如果您之后需要重置某些控件,请按以下方式进行:
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { progressBar1.Invoke((Action) (() => { progressBar1.MarqueeAnimationSpeed = 0; progressBar1.Style = ProgressBarStyle.Continuous; })); }
在这个例子中,完成之后,一个进度条被重置。
重要提示:每当你访问一个GUI控件,就像我在上面的例子中那样使用Invoke。 使用Lambda可以很容易,就像你在代码中看到的一样。