问题
i want to show Temporary files in a datagrid , hence it is a long term process i use background worker in my C# .net WPF application .
my Code is
private System.ComponentModel.BackgroundWorker _background = new System.ComponentModel.BackgroundWorker();
private void button1_Click(object sender, RoutedEventArgs e)
{
_background.RunWorkerAsync();
}
public MainWindow()
{
InitializeComponent();
this._background.DoWork += new DoWorkEventHandler(_background_DoWork);
this._background.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(_background_RunWorkerCompleted);
this._background.WorkerReportsProgress = true;
_background.WorkerSupportsCancellation = true;
}
void _background_DoWork(object sender, DoWorkEventArgs e)
{
this.Dispatcher.Invoke((Action)(() =>
{
try
{
FileInfo[] files = new
DirectoryInfo(System.IO.Path.GetTempPath()).GetFiles();
foreach (FileInfo fi in files)
{
if (fi != null)
{
dataGrid1.Items.Add(fi);
}
}
}
catch { }
}));
}
void _background_RunWorkerCompleted(object sen, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Cancelled");
}
else if (e.Error != null)
{
MessageBox.Show("Exception Thrown");
}
}
All the code is running but it hangs when datagrid is loading means my UI does not response when program is running .
What modification is needed to run background worker smoothly in the above condition ?
Beside it , if i want to add a ProgressBar which progressed along with this application then what i have to do ?
Thank You
回答1:
By using this.Dispatcher.Invoke
, you are effectively marshalling the work back to the UI thread. This makes no sense: you are blocking the UI thread while this action is being performed.
Split the work in two parts:
- the slow part, which is retrieving the files and should be done outside the
Dispatcher.Invoke
- the UI update, which must be done in
Dispatcher.Invoke
, or (better) in theRunWorkerCompleted
event handler.
The background worker component is made exactly so that you don't need to dispatch UI work manually using the dispatcher. For example, you could store the files in a field which you fill in the DoWork
method, and use to fill the datagrid in the RunWorkerCompleted
event:
FileInfo[] files;
void _background_DoWork(object sender, DoWorkEventArgs e)
{
files = new DirectoryInfo(System.IO.Path.GetTempPath()).GetFiles();
}
void _background_RunWorkerCompleted(object sen, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Cancelled");
}
else if (e.Error != null)
{
MessageBox.Show("Exception Thrown");
}
else
{
foreach (FileInfo fi in files)
{
dataGrid1.Items.Add(fi);
}
}
}
Note: if you are using C# 5, you now have an even easier way, using the async/await
feature. All you need is something like this:
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
try
{
var files = await Task.Run(() =>
new DirectoryInfo(System.IO.Path.GetTempPath()).GetFiles()
);
foreach (var f in files)
this.dataGrid1.Items.Add(f.Name);
}
catch (Exception e)
{
MessageBox.Show("Exception thrown!"); // do proper error handling here...
}
finally
{
button1.Enabled = true;
}
}
All the rest of the cruft is taken care of by the compiler.
回答2:
Try to take this action off the Dispatcher:
FileInfo[] files = new DirectoryInfo(System.IO.Path.GetTempPath()).GetFiles();
It should only do small and quick operations that involve UI access or modification. See this link: http://msdn.microsoft.com/en-us/magazine/cc163328.aspx
Your heavy work can be done by the BackgroundWorker and use the Dispatcher to update the dataGrid.Items collection.
Try to use the Dispatcher using:
Dispatcher.BeginInvoke()
回答3:
Use DirectoryInfo.EnumerateFiles.
This line:
this.Dispatcher.Invoke
executes code synchronously at main thread, so there are no benefits from using of BackgroudWorker
such a way, because DirectoryInfo.GetFiles
returns only when all files in directory are enumerated.
On the other hand, DirectoryInfo.EnumerateFiles
is lazy. You can write something like this:
void _background_DoWork(object sender, DoWorkEventArgs e)
{
var info = new DirectoryInfo(System.IO.Path.GetTempPath());
// now enumeration happens in background
foreach (var fi in info.EnumerateFiles())
{
// main thread in used only when there is next enumeration result available
Dispatcher.Invoke((Action)(() => dataGrid1.Items.Add(fi)));
}
}
回答4:
You should be updating the UI in either the RunWorkerComplete or ProgressChanged event handlers.
Try something like this:
public Program()
{
w = new BackgroundWorker();
w.DoWork += new DoWorkEventHandler(w_DoWork);
w.ProgressChanged += new ProgressChangedEventHandler(w_ProgressChanged);
w.RunWorkerCompleted += new RunWorkerCompletedEventHandler(w_RunWorkerCompleted);
w.WorkerReportsProgress = true;
w.WorkerSupportsCancellation = true;
w.RunWorkerAsync();
}
void w_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
FileInfo[] files = e.Result as FileInfo[];
foreach (FileInfo fi in files)
{
//dataGrid1.Items.Add(fi);
}
}
void w_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
FileInfo fi = e.UserState as FileInfo;
//dataGrid1.Items.Add(fi);
}
void w_DoWork(object sender, DoWorkEventArgs e)
{
var w = sender as BackgroundWorker;
FileInfo[] files = new DirectoryInfo(
Path.GetTempPath()).GetFiles();
// Using ProgressChanged
foreach (FileInfo fi in files)
{
w.ReportProgress(0, fi);
}
// Using RunWorkerCompleted
e.Result = files;
}
Also, there is no need for the try/catch in dowork, exceptions are automatically caught and reported as errors in the runworkercomplete event.
来源:https://stackoverflow.com/questions/12706651/background-worker-does-not-work-properly-in-wpf