Scheduling a single-fire event
I'm writing an application which requires a status bar update for while a database is being loaded. I set the labe开发者_高级运维l to "Database loading..." then load the database with a BackgroundWorker
. When the worker completes, I set the label to "Database loaded." This is only done on startup, however, and I don't want the label to be visible for much longer, and would like it cleared several seconds after the worker completes. I could put a dedicated timer object on my main for, but this single action would be its only job, and it seems like a more elegant solution should exist. So I tried lambdas:
void dataLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
DataLabel.Text = "Database Loaded";
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
timer.Interval = 5000;
timer.Tick += new EventHandler((o, a) =>
{
DataLabel.Text = "";
(o as System.Windows.Forms.Timer).Enabled = false;
});
}
Of course timer
's scope expires after the function exits, and it's Tick
event is never called.
How can I get a simple, single-fire event to run without the use of global-instance timers?
Your code is mostly fine. In fact, your general architecture is the most elegant solution in my opinion. You just forgot to start the timer. You do not need to worry about the GC prematurely collecting the timer because it will actually "root" itself automatically when it is started. That of course begs the question of whether or not this would cause a memory leak since a new timer is created everytime. I think not since the timer will also "unroot" itself when it is stopped. So the following should work fine.
void dataLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
DataLabel.Text = "Database Loaded";
var timer = new System.Windows.Forms.Timer();
timer.Interval = 5000;
timer.Tick += (o, a) =>
{
timer.Stop();
DataLabel.Text = "";
};
timer.Start();
}
Maybe you can use async invocation of some method with Thread.Sleep(N) in its body.
You can create a class variable Timer
, use it once and then null
it.
Also, you could register a method with the ThreadPool - in this thread, sleep for the desired amount before triggering a call to the UI to update the label.
Or you could re-use the background worker for the same effect. This saves you needing to Control.Invoke
onto the UI thread...
Usually, even in Visual studio, the status persists until user do another action. And that seems to be a better approach, as it may be possible that user wont be able to check the status in 5 or 10 second. I think its better to update the status when use do some another action (like clicking menu etc)
You could probably just make the background thread wait a bit longer, e.g.:
void dataLoader_DoWork(object sender, DoWorkEventArgs e) {
// Do work normally.
// Report progress as complete.
var worker = sender as BackgroundWorker;
worker.ReportProgress(100);
Thread.Sleep(5000);
}
void dataLoader_ProgressChanged(object sender, ProgressChangedEventArgs e) {
if (e.ProgressPercentage == 100) {
// Set label here
DataLabel.Text = "Database Loaded";
}
}
void dataLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
DataLabel.Text = "";
}
This might work a little better than firing of an async method, as a thread has already been created for the background worker anyways. Would that work better?
Queue a WorkItem in the ThreadPool, Sleep for 5 seconds, and then BeginInvoke
the update behavior. Here's a basic example:
using System;
using System.Threading;
using System.Windows.Forms;
namespace CSharpScratch
{
class Program
{
private static void Main()
{
var myForm = new MyForm();
myForm.ShowDialog();
}
}
class MyForm : Form
{
private readonly Label _label;
public MyForm()
{
_label = new Label {Text = "Hello", Parent = this};
Load += FormLoaded;
}
public void FormLoaded(object sender, EventArgs args)
{
ThreadPool.QueueUserWorkItem(x =>
{
Thread.Sleep(5000);
BeginInvoke(new Action(() => _label.Text = "Goodbye"));
});
}
}
}
精彩评论