I am trying to figure out the best way to keep my application responsive. Below shows the code that I am currently working with. What i have found is that the Background worker thread is the way to go.
private void cleanFiles()
{
if (listView1.CheckedItems.Count != 0)
{
// If so, loop through all checked files and delete.
foreach (ListViewItem item in listView1.CheckedItems)
{
string fileName = item.Text;
string filePath = Path.Combine(tFile + fileName);
try
{
File.Delete(filePath);
}
catch (Exception)
{
//ignore files being in use
}
MessageBox.Show("Files Cleaned");
}
}
else
{
MessageBox.Show("Please put a check by the files you want to delete");
}
}
}
}
The easiest way to keep your program responsive is to use the BackgroundWorker.
List<string listWithFilenames = new List<string>();
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync(listWithFilenames);
See the documentation.
Have a look at these questions:
Cross-Threading issue with listView
How to execute code in the GUI Thread
assuming that the method you posted runs under the context of the UI thread,
all you need to do is wrap the logic (the foreach part)
in a method like :
private void DeleteFiles(object state)
{
/// your logic here
}
and call the ThreadPool.QueueWorkItem(new WaitCallback(DeleteFiles));
from the cleanfiles method.
if you run .NET 4.0, you can use something like:
Task myTask = Task.Factory.StartNew( () => DoWork(null));
then check the myTask status later , to see if its done.
Related
I have a background worker that does some work. I want to have a spinner on main control indicating that app is working. But looks like threading is preventing my spinner from animating (sometimes it doesnt even show)... can some one explain why it is not working (probably because sleeping the thread) and perhaps guide me to a solution with minimal code changes :)
Best regards, no9.
public void StartProcess(object obj)
{
this.eventAggregator.GetEvent<ActionEvent>().Publish(new Message(EMessageType.Info)
{
Title = "Start",
Description = "Starting action..."
});
Worker = new BackgroundWorker();
Worker.DoWork += new DoWorkEventHandler(worker_DoWork);
Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
try
{
this.Document = null;
Dictionary<string, Stream> tmp = this.GetContent();
//start and show the spinner
this.View.ShowDocumentProgressSpinner(true);
Worker.RunWorkerAsync(tmp);
}
catch (Exception ex)
{
ExceptionPolicy.HandleException(ex, "LogAndSwallow");
this.eventAggregator.GetEvent<ActionEvent>().Publish(new Message(EMessageType.Error)
{
Title = "Error",
Description = "There was an error processing your action."
});
}
finally
{
this.View.ShowActionButton(false);
}
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
var logger = new ActionLoggerAndViewUpdater(this.eventAggregator, this.View);
foreach (KeyValuePair<string, Stream> pair in (Dictionary<string, Stream>)e.Argument)
{
using (Stream stream = pair.Value)
{
//setting the document fires login event that changes stuff on presenter (current class instance)
this.Document = new Document(stream);
//check if ok to continue
while (!this.IsLoggedInForTheWorkingDocument)
//wait of the login stuff to complete
Thread.Sleep(2000);
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
//this depends on the login and takes some time to process
this.DoSomeStuff();
}));
}
}
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//stop and hide the spinner
this.View.ShowDocumentProgressSpinner(false);
...
}
Why do you invoke something on your background worker? The background workers whole purpose is to not run in the UI thread and block it. Do not invoke a long running process from the worker, it defeats it's purpose.
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
var logger = new ActionLoggerAndViewUpdater(this.eventAggregator, this.View);
foreach (KeyValuePair<string, Stream> pair in (Dictionary<string, Stream>)e.Argument)
{
using (Stream stream = pair.Value)
{
this.Document = new Document(stream);
// this should REALLY be handled by an event, not busy waiting:
while (!this.IsLoggedInForTheWorkingDocument)
//wait of the login stuff to complete
Thread.Sleep(2000);
// removed the invoking, this is supposed to run in the background, right?
this.DoSomeStuff();
}
}
}
Because your worker runs in another thread, it cannot update the UI thread as it progresses, hence the behaviour. See this other, similar question Updating GUI (WPF) using a different thread.
Essentially you need to call control.Dispatcher.Invoke to get your UI to update from the other thread
Basically, this is what happens. I have a thread(endless loop) that runs as a background process while the form is showing. The thread checks if there is a need to add a new ToolStripMenuItem.
If the conditions are met, I'll need to use Invoke in order to create the UI object right? Problem with this is, when the this.Invoke or BeginInvoke is called, the form became unresponsive while the thread that does the checking is still running fine. Any ideas?
This is the first time i'm trying with this multithreading thingee. I'm sure i've missed out something.
public void ThreadSetCom()
{
while (true)
{
string[] tmpStrPort = System.IO.Ports.SerialPort.GetPortNames();
IEnumerable<string> diff = tmpStrPort.Except(strPort);
strPort = tmpStrPort;
System.Console.WriteLine(System.IO.Ports.SerialPort.GetPortNames().Length);
foreach (string p in diff)
{
var cpDropdown = (ToolStripMenuItem)msMenu.Items["connectToolStripMenuItem"];
cpDropdown = (ToolStripMenuItem)cpDropdown.DropDownItems["connectReaderToolStripMenuItem"];
ToolStripMenuItem tsmi = new ToolStripMenuItem();
tsmi.Text = p;
tsmi.Name = p;
tsmi.Click += new EventHandler(itm_Click);
if (this.msMenu.InvokeRequired)
{
GUIUpdate d = new GUIUpdate(ThreadSetCom);
this.Invoke(d);
}
else
{
cpDropdownList.DropDownItems.Add(tsmi);
}
}
}
}
Your ThreadSetCom method never exits:
while (true)
... with no return or break statements. That's going to hang the UI thread forever.
It's not clear what you're trying to achieve, but you definitely don't want to be looping like that in the UI thread. I'd argue that you don't want to be looping like that in a tight way in any thread, mind you...
I think a better approach for you would probably be to use a BackgroundWorker. I say that because what you're experiencing isn't that uncommon when doing multi-threading in a Windows Forms application. Further, the BackgroundWorker is able to manage the thread switching properly. Let me give you an example of that code with the BackgroundWorker.
Build a private class variable
private BackgroundWorker _worker;
Add to the CTOR
public {ctor}()
{
_worker = new BackgroundWorker();
_worker.WorkerSupportsCancellation = true;
_worker.WorkerReportsProgress = true;
_worker.DoWork += new DoWorkEventHandler(BackgroundThreadWork);
_worker.ProgressChanged += new ProgressChangedEventHandler(BackgroundThreadProgress);
}
DoWork handler
private void BackgroundThreadWork(object sender, DoWorkEventArgs e)
{
while (!_worker.CancellationPending)
{
string[] tmpStrPort = System.IO.Ports.SerialPort.GetPortNames();
IEnumerable<string> diff = tmpStrPort.Except(strPort);
strPort = tmpStrPort;
System.Console.WriteLine(System.IO.Ports.SerialPort.GetPortNames().Length);
foreach (string p in diff)
{
_worker.ReportProgress(1, p);
}
}
}
Report progress handler
private void BackgroundThreadProgress(object sender, ReportProgressEventArgs e)
{
var cpDropdown = (ToolStripMenuItem)msMenu.Items["connectToolStripMenuItem"];
cpDropdown = (ToolStripMenuItem)cpDropdown.DropDownItems["connectReaderToolStripMenuItem"];
ToolStripMenuItem tsmi = new ToolStripMenuItem();
tsmi.Text = e.UserState as string;
tsmi.Name = e.UserState as string;
tsmi.Click += new EventHandler(itm_Click);
cpDropdownList.DropDownItems.Add(tsmi);
}
The Loop
However, one thing you're going to have to do is figure out how to get out of this loop. When should it exit? Whatever that means, you need to add to the if statement that exists there in my example because this loop will never end otherwise.
What the effect of this code snippet:
GUIUpdate d = new GUIUpdate(ThreadSetCom);
this.Invoke(d);
is that the method 'ThreadSetCom' will be invoked in the UI thread. And there is an infinitive loop in that method. That is why your form becomes unresponsive.
I suggest you that you should move the foreach clause to a separate method and invoke this method in the UI thread when the condition is hit, for example the diff.Count>0.
I have a Winforms application which is working fine.. using a BackgroundWorkerThread to manage GUI usability during processing of serial data to a device.
It's working fine.
Now, I am adding a new method, and copying what I did in other forms. But I am getting a cross thread exception.
I declare my BWT like this:
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += DownloadGpsDataFromDevice;
bw.WorkerReportsProgress = true;
bw.RunWorkerAsync();
I then have a method delared like this, which does the background work:
private void DownloadGpsDataFromDevice(object sender, DoWorkEventArgs e)
{
_performScreenUpdate = true;
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
Common.WriteLog("Extracting raw GPS data. Sending LL.");
ReplyString raw = DeviceServices.ExecuteCommand("$LL");
The DeviceServices.ExecuteCommand("$LL"); is the bit that does the work, but I am getting the exception on the previous line, where I log to a text file. Now, that makes you worry - writing to a file. However, I have done this thousands of times in another BWT.
I made the writing thread safe. Here this my Common.WriteLog method:
public static void WriteLog(string input)
{
lock (_lockObject)
{
WriteLogThreadSafe(input);
}
}
private static void WriteLogThreadSafe(string input)
{
Directory.CreateDirectory(LogFilePath);
StreamWriter w = File.AppendText(LogFilePath + #"\" + LogFileName);
try
{
w.WriteLine(string.Format("{0}\t{1}", DateTime.Now, input));
}
catch (Exception e)
{
System.Console.WriteLine("Error writing to log file!");
System.Console.WriteLine("Tried to write: [" + input + "]");
System.Console.WriteLine("Failed with error: [" + e.Message + "]");
}
finally
{
w.Close();
}
}
This have been working for ages. I don't believe the error is there. I think I am just missing something on the call maybe?
You cannot change UI elements from BackgroundWorker thread. You'll have to marshall back to UI thread by calling Invoke().
Try this
private void DownloadGpsDataFromDevice(object sender, DoWorkEventArgs e)
{
_performScreenUpdate = true;
Invoke((MethodInvoker)(() => {
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
});
...
The issue is that you are updating UI elements from non-UI thread:
Those lines should not be inside of DownloadGpsDataFromDevice
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
To take advantage of BackgroundWorker run method bw.ReportProgress(0);. Update UI in ProgressChanged handler, which was specifically designed for this purpose.
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage = 0)
{
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
}
}
Some instances can't or should not be accessed by multiple threads. You have two options to protect your data from cross thread exceptions.
You can lock your object when you access it from multiple threads with a lock:
object locker = new object();
SomeObject MyObject = new SomeObject();
private void FromMultipleThread()
{
lock(locker)
{
MyObject = OtherObject;
}
}
Your second option is to lock your thread with a ManualResetEvent. It is very simple, you only have to call WaitOne() from you ManualResetEvent to lock your thread while an other thread access your "cross threaded" Object.
In your case, you would want to change your UI from the reportProgress of your backgroundWorker. The reportProgress will come back to the initial thread, then you can modify your UI.
Are you sure that is the right line? I don't think you should be able to update the ui in your worker. Try commenting out the gui update and clean and build your solution to see if the logging is really the problem. To update the ui, set WorkerReportsProgress and create an event handler for that to update the ui and report progress in the worker.
How do I get results from the BackgroundWorker in this case? I'm also open to doing things in in alternative manner(such as not using BackgroundWorker). The goal is to do all my jobs in parallel, starting at the same time. I actually honestly don't know if all jobs will be completed in parallel using bw. I'm still learning this threading stuff. I'm using WPF/XAML (I'm pretty sure that makes a big difference on how threading type code is written).
namespace JobFactory
{
public partial class MainWindow : Window
{
MainWindow()
{
InitializeComponent();
Manager boss = new Manager();
string[] reports = boss.runWorkers(50);
}
}
}
namespace Workers
{
class Manager
{
public reports[] runWorkers(int numWorkers)
{
BackgroundWorker worker = new BackgroundWorker();
for (int i = 0; i < numWorkers; i++)
{
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
string report = this.job();
};
}
worker.RunWorkerAsync();
//Return reports here...
}
public string job()
{
Thread.Sleep(2000);
return "Job Completed";
}
}
}
You could try Task in .NET 4.0 System.Threading.Tasks
After you call StartNew main thread continues in parallel doing whatever you want it to do, then once it reaches a point where return value is required in Main Thread, main thread is blocked till the Result is returned by the method called on the other thread. If the result is already returned by the main thread reaches the WriteLine there is no blocking.
Task task = Task.Factory.StartNew(SomeMethod);
Console.WriteLine(task.Result);
public static string SomeMethod()
{
return "Hello World";
}
OR
Task task = Task.Factory.StartNew(() => { return "Hello World"; } );
Console.WriteLine(task.Result);
Check this blog for more interesting samples.
EDIT
After below (rather frustrating) discussion I had to make an edit to this answer to justify a right answer.
in the .NET Framework 4, tasks are the preferred API for writing multi-threaded, asynchronous, and parallel code. Check MSDN
Your best bet is to let the whole thing run asynchronously. If you don't let runWorkers return until all the workers are done, then you're giving up the primary benefit of asynchronous operations, which is that you can do other things (like respond to other events) while they're running.
A few suggestions toward that end:
Create an ObservableCollection to hold the reports. With observable collections, you can bind UI elements to it and they will automatically update as the collection changes. It is also possible to programmatically capture the collection's CollectionChanged event if you need to know when it changes. A word of caution, though - never modify this collection from inside the DoWork procedure!
You will need to create a different BackgroundWorker for each report. If you try to run a BackgroundWorker that's already working, you'll get an exception. However, be aware that starting a very large number of BackgroundWorkers simultaneously might cause the system to thrash a bit. In those cases you might want to look into using ThreadPool instead.
Attach a RunWorkerCompleted event handler to each BackgroundWorker. This event handler should unpack the results of the RunWorkerCompletedEventArgs's Result property, and add it to the collection. If the BackgroundWorker was started on the main thread, then this event is guaranteed to be raised on the main thread, so it should be safe to update the collection from this event handler.
Here's a rough sketch of how you might do it:
class Manager
{
public ObservableCollection<string> Reports { get; private set; }
public void runWorkers(int numWorkers)
{
for (int i = 0; i < numWorkers; i++)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync(i);
}
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = Job((int)e.Argument);
}
public void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Error != null)
{
// handle error
}
else
{
Reports.Add(e.Result as string);
}
}
private string Job(int jobID)
{
Thread.Sleep(2000);
return string.Format("Job {0} Completed", jobID);
}
}
I'm currently writing a little GUI program that does some work and exits afterwards. While work is done, the GUI thread is updated with infos for the user.
This is the pattern I'm currently using and I'm thinking it's not the most elegant one:
static void MainForm_Loaded(BeoExport exporter)
{
// Thread 1 runs the Export
workerThread = new Thread(() =>
{
exporter.StartExport();
// don't exit immediately, so the user sees someting if the work is done fast
Thread.Sleep(1000);
});
// Thread 2 waits for Thread 1 and exits the program afterwards
waiterThread = new Thread(() =>
{
workerThread.Join();
Application.Exit();
});
workerThread.Start();
waiterThread.Start();
}
So what pattern/mechanics would you use to do the same?
To clarify: I was not interested in a way to update the GUI thread. That's already done. This might sound esoteric but I was lookig for the right way to quit the application.
If I could, I would give Dave the credits, since he pointed out the usefulness of the BackgroundWorker.
Have you considered a BackgroundWorker thread instead? You can use its ReportProgress method and ProgressChanged event to update the GUI (with a progress bar perhaps), assuming that you can refactor BeoExport.StartExport method to also report progress. This gives the users visible feedback that work is actually happening.
I don't understand why do you use two threads. You can use threadpool:
ThreadPool.QueueUserWorkItem((state)=>{
exporter.StartExport();
Thread.Sleep(1000);
Application.Exit();
});
I suggest you to use the BackgroundWorker class. It's thought to do the kind of job you're doing. You could do domething like this:
public class Form1 : Form
{
private BackgroundWorker worker;
private ProgressBar bar;
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
bar= new ProgressBar();
bar.Dock = DockStyle.Top;
Controls.Add(bar);
worker = new BackgroundWorker();
worker.WorkerReportsProgress=true;
worker.RunWorkerCompleted += delegate
{
Close();
};
worker.ProgressChanged += delegate(object sender, ProgressChangedEventArgs ev)
{
bar.Value = ev.ProgressPercentage;
};
worker.DoWork += worker_DoWork;
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
//do your work here. For the example, just sleep a bit
//and report progress
for (var i = 0; i < 100;i++ )
{
Thread.Sleep(50);
worker.ReportProgress(i);
}
}
}
You can use an AutoResetEvent. The main thread waits for the autoreset event to be reset.
var wh = new AutoResetEvent(false);
var workerThread = new Thread(() =>
{
exporter.StartExport();
// don't exit immediately, so the user sees something if the work is done fast
Thread.Sleep(5000);
wh.Set();
});
workerThread.Start();
wh.WaitOne();
Application.Current.Shutdown();
Have you taken a look at the Task Parallel Library in .net 4 you can set up a task and the library will work out to best pararellise it for you, either threading, working a seperate CPU core's the is a load of great information about it online.
Regards
Iain
To add a little to Lain's answer, here's a Console sample using a Task from the System.Threading.Tasks namespace.
class Program
{
static void Main(string[] args)
{
Task<int> task = Task<int>.Factory.StartNew(() =>
{
Exporter exporter = new Exporter();
int i = exporter.StartExport();
return i;
});
int iResult = task.Result;
Console.WriteLine(iResult);
Console.ReadLine();
}
class Exporter {
public int StartExport()
{
//simulate some work
System.Threading.Thread.Sleep(500);
return 5;
}
}
}
Using a BackgroundWorker might help you implement your background processing. If you wanted to stick with your current pattern then consider the following.
static void MainForm_Loaded(BeoExport exporter)
{
workerThread = new Thread(() =>
{
exporter.StartExport();
Thread.Sleep(1000);
MainForm.BeginInvoke(
(Action)(() =>
{
MainForm.Close();
});
});
workerThread.IsBackground = true;
workerThread.Start();
}
Have the worker thread send a notification message of some description to the main thread. The GUI can then either exit or display a "done" message as appropriate.