Please see the following code:
foreach(string url in urls)
{
//Method that will process url ProcessUrl(url)
//Add eached proccessed url to a treelist
}
ProcessUrl method have HttpWebRequest and HttpWebResponse so sometime it takes a nudge and if there were many links it will take time which will hang my program.
I can't actually suggest a solution of think of one, because i may based it on something wrong so what i want is to make this code runs while i can operate 100% in my program without any crashes or hangs, and that each newly processed link will be inserted to the treelist without any lag.
If you want to perform a long-running operation in the background and pass the results of the operation back to the UI as they become available, while at the same time the UI stays responsive, then it's straightforward to use BackgroundWorker here.
void BeginExpensiveOperation()
{
var worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += ExpensiveWork;
worker.ProgressChanged += WorkerOnProgressChanged;
List<string> urls = new List<string> { "http://google.com" };
worker.RunWorkerAsync(urls);
}
// runs in a worker thread
void ExpensiveWork(object sender, DoWorkEventArgs e)
{
var worker = (BackgroundWorker)sender;
var urls = (List<string>) e.Argument;
foreach (var url in urls)
{
//TODO: do your work here synchronously
var result = new WebClient().DownloadString(url);
//TODO: pass the result in the userState argumetn of ReportProgress
worker.ReportProgress(0, result); // will raise worker.ProgressChanged on the UI thread
}
}
private void WorkerOnProgressChanged(object sender, ProgressChangedEventArgs progressChangedEventArgs)
{
//this executes on the UI thread
var value = progressChangedEventArgs.UserState;
//TODO: use result of computation to add it to the UI
panel.Children.Add(new TextBlock {Text = value.ToString()});
}
Fill in your problem-specific code in the //TODO: placeholders and call BeginExpensiveOperation() to start the operation asynchronously.
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
I'm having trouble with my program flow in a while loop I created.
while (reader.Read())
{
// Store scenario information
int Id = (int)reader["ScenarioID"];
string Data = reader["ScenarioData"].ToString();
string Url = "http://google.com";
// Initialize result information
int HasSucceeded = 0;
var screenshot = new Byte[] { };
// Navigate to webBrowser
webBrowser2.Navigate(Url);
webBrowser2.DocumentCompleted += WebBrowserDocumentCompleted;
// Do test
TestScenarios(Url, HasSucceeded);
// Take screenshot
TakeScreenshot(screenshot);
// Insert results
InsertResults(Id, HasSucceeded, screenshot);
// Mark scenario for deletion
MarkScenario(Id);
}
private void WebBrowserDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs Url)
{
MessageBox.Show("Operation has completed!");
}
The expected flow of the program should be
Read an item in the table
Initialize some variables/store some values
Navigate the webBrowser control toe the URL
When the webBrowser control is finished, do a test
Take a screenshot
Insert results into new table
Mark the item in the original table for deletion
Loop back to #1 until all items have been covered.
However, what is happening is everything in the while loop is running properly in order except for the webBrowser2.Navigate line, which does not show the Url until the while loop has exited. Immediately after the Url shows, 5 sequential messages "Operation has completed" (for the 5 items in the table) appear. How can I fix my flow?
Try this solution. Wrap your loop in another thread than UI thread. then make use of AutoResetEvent
new Thread(() =>
{
AutoResetEvent signal = new AutoResetEvent(false);
while (reader.Read())
{
// Store scenario information
int Id = (int)reader["ScenarioID"];
string Data = reader["ScenarioData"].ToString();
string Url = "http://google.com";
// Initialize result information
int HasSucceeded = 0;
var screenshot = new Byte[] { };
Action action = () =>
{
webBrowser2.Tag = signal;
// Navigate to webBrowser
webBrowser2.Navigate(Url);
webBrowser2.DocumentCompleted -= WebBrowserDocumentCompleted;
webBrowser2.DocumentCompleted += WebBrowserDocumentCompleted;
};
webBrowser2.Invoke(action);
signal.WaitOne();//Wait till it finishes
// Do test
TestScenarios(Url, HasSucceeded);
// Take screenshot
TakeScreenshot(screenshot);
// Insert results
InsertResults(Id, HasSucceeded, screenshot);
// Mark scenario for deletion
MarkScenario(Id);
}
}).Start();
private void WebBrowserDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs Url)
{
MessageBox.Show("Operation has completed!");
((AutoResetEvent)((WebBrowser)sender).Tag).Set();
}
I asked worker thread to wait till the document loads then continue execution. simple.
Hope this helps
The Navigate method is probably queuing an event which will be later handled on the same thread your code is running in (the UI thread). You may have to put your code into a separate background worker thread to allow the UI events to be processed before your loop is finished.
I recomend you to ckeck the async and await operation if you are devleloping in .NET 4.5 Frammework. This propably will solve your problem.
Async and Await in MSDN
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'm trying to code part of my application that runs a BackgroundWorker process that performs a time-consuming operation. In the main thread, a timer updates a progress bar (this is a continuation of this question). However, this code display no MessageBoxes. Setting a breakpoint on the foreach (String word in this.words) line in the SearchButton_Click event handler reveals that this.words has no values, i.e. this.words.Count() == 0.
public partial class Form1 : Form
{
System.Windows.Forms.Timer searchProgressTimer;
List<String> words;
public Form1()
{
InitializeComponent();
words = new List<String>(3);
}
private void SearchDatabase_Click(object sender, EventArgs e)
{
this.searchProgressTimer.Start();
SearchBackgroundWorker.RunWorkerAsync();
foreach (String word in this.words) // BREAKPOINT HERE
MessageBox.Show(word);
}
private void SearchBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Time-consuming operation
String filename = #"http://www.bankofengland.co.uk/publications/Documents/quarterlybulletin/qb0704.pdf";
WebClient webClient = new WebClient();
webClient.DownloadFileAsync(new Uri(filename), #"file.pdf");
List<String> word_result = new List<String> { "word1", "word2", "word3" };
e.Result = word_result; // e.result is an Object, and word_result is a List.
}
private void SearchBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.searchProgressTimer.Stop();
this.searchProgressBar.Value = 0;
this.words = (List<String>)e.Result;
}
}
My guess as to why this occurs is because the BackgroundWorker thread isn't finished with its operation before the main UI thread moves on to the foreach loop. I think I understand that part. However, since I want to perform the time-consuming operation in a background thread so the progress bar can update its value as said operation runs, then use the result of the BackgroundWorker immediately after its finished, how would I do this?
Please edit my title if it doesn't get the point across as well. I wasn't sure how to phrase this.
Do whatever you want to do in that RunWorkerCompleted event:
private void SearchBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.searchProgressTimer.Stop();
this.searchProgressBar.Value = 0;
this.words = (List<String>)e.Result;
foreach (String word in this.words) // BREAKPOINT HERE
MessageBox.Show(word);
}
Since you are getting this information from the background worker, the only way you know you have your list is when the worker completes.
I'm trying to use a Background Worker in a WPF application. The heavy lifting task uses WebClient to download some HTML and parse some info out of it. Ideally I want to do that downloading and parsing without locking the UI and placing the results in the UI once it's done working.
And it works fine, however, if I quickly submit the "download and parse" command, I get the error:
This BackgroundWorker is currently busy and cannot run multiple tasks
concurrently
So I did some Googling and it seems that I can enable the .WorkerSupportsCancellation property of the background worker and just .CancelAsync(). However, this doesn't work as expected (canceling the current download and parse).
I still get the above error.
Here's my code:
//In window constructor.
_backgroundWorker.WorkerSupportsCancellation = true;
_backgroundWorker.DoWork += new DoWorkEventHandler(_backgroundWorker_DoWork);
_backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_backgroundWorker_RunWorkerCompleted);
//Declared at class level variable.
BackgroundWorker _backgroundWorker = new BackgroundWorker();
//This is the method I call from my UI.
private void LoadHtmlAndParse(string foobar)
{
//Cancel whatever it is you're doing!
_backgroundWorker.CancelAsync();
//And start doing this immediately!
_backgroundWorker.RunWorkerAsync(foobar);
}
POCOClassFoo foo = new POCOClassFoo();
void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//This automagically sets the UI to the data.
Foo.DataContext = foo;
}
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//DOING THE HEAVY LIFTING HERE!
foo = parseanddownloadresult()!
}
Calling CancelAsync will still fire the RunWorkerCompleted event. In this event, you need to make sure that CancelAsync has not been called, by checking e.Cancelled. Until this event fires, you cannot call RunWorkerAsync.
Alternatively, I would recommend you do what Tigran suggested and create a new BackgroundWorker each time.
Further more, I would recommend storing the results of_backgroundWorker_DoWork in e.Result, then retrieve them from the same in _backgroundWorker_RunWorkerCompleted
Maybe something like this
BackgroundWorker _backgroundWorker;
private BackgroundWorker CreateBackgroundWorker()
{
var bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += _backgroundWorker_DoWork;
bw.RunWorkerCompleted += new _backgroundWorker_RunWorkerCompleted;
return bw.
}
private void LoadHtmlAndParse(string foobar)
{
//Cancel whatever it is you're doing!
if (_backgroundWorer != null)
{
_backgroundWorker.CancelAsync();
}
_backgroundWorker = CreateBackgroundWorker();
//And start doing this immediately!
_backgroundWorker.RunWorkerAsync(foobar);
}
//you no longer need this because the value is being stored in e.Result
//POCOClassFoo foo = new POCOClassFoo();
private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
//Error handling goes here.
}
else
{
if (e.Cancelled)
{
//handle cancels here.
}
{
//This automagically sets the UI to the data.
Foo.DataContext = (POCOClassFoo)e.Result;
}
}
private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//DOING THE HEAVY LIFTING HERE!
e.Result = parseanddownloadresult()!
}
The thing is that CancelAsync() does what it climes: cancel in async way. That means that it will not stop immediately, but after some time. That time can never be calculated or predicted, so you have a couple of options:
Wait until this backround worker stops really, by waiting in cycle until IsBusy property of it becomes false
Or, I think, better solution is to start another background worker, considering that request of cancelation was already sent to the first one, so it will be soon or later stop. In this case, you need to know from which background worker data comes, in order to process it or not, cause on start of second the first one will still run and pump the data from WebService.
Hope this helps.
CancelAsync returns before the worker cancels and stops its work. Hence, your RunWorkerAsync call is starting before the worker is ready, and you're getting that error. You'll need to wait for the worker to be ready first.
When I'm not interested in tracking progress of an async operation, I tend to prefer to just slap a lambda at ThreadPool.QueueUserWorkItem instead of instantiating and setting up a background worker that I have to check the state of to be able to reuse in a sane way.
You need to verify before you kicks in.
f( !bw.IsBusy )
bw.RunWorkerAsync();
else
MessageBox.Show("Can't run the bw twice!");
You are calling CancelAsync without waiting for the background worker to actually cancel the work. Also you must have your own logic for cancelling the work. There is a good example on MSDN which shows how to do it. Basically in your parseanddownloadresult() method you need to check the CancellationPending property.