Prevent form from freezing - c#

I'd like to download a picture and afterwards show it in a picturebox.
at first I did like this:
WebClient client = new WebClient();
client.DownloadFile(url, localFile);
pictureBox2.Picture = localFile;
But that wasn't perfect because for the time while the download is performed the app is kinda freezing.
Then I changed to this:
public class ParamForDownload
{
public string Url { get; set; }
public string LocalFile { get; set; }
}
ParamForDownload param = new ParamForDownload()
{
Url = url,
LocalFile = localFile
};
ThreadStart starter = delegate { DownloadMap (param); };
new Thread(starter).Start();
pictureBox2.Picture = localFile;
private static void DownloadMap(ParamForDownload p)
{
WebClient client = new WebClient();
client.DownloadFile(p.Url, p.LocalFile);
}
But now I have to do something like a "wait for thread ending" because the file is accessed in the thread and to same time there's downloaded something to the file by the DownloadMap method.
What would be the best wait to solve that problem?

Basically, What was happening originally, was the UI Thread was completing the download, and because it was working away on that, it couldn't be refreshed or painted (working synchronously). Now what is happening is that you're starting the thread then the UI thread is continuing, then trying to assign the local file (which hasn't finished downloading) to the picture box. You should try either of the following:
You should use a background worker to complete your download task.
It has events that will be very handy. DoWork, where you can start the download.
There is also a RunWorkerCompleted event that is fired when the Work has completed. Where you can set the image there (pictureBox2.Picture = localFile;).
It's definitely worth checking out, I think it's the most appropriate way to complete what you are trying to achieve.
Or
If you want to stick with using a Thread. You could take out the Image assignment after you've done the Thread.Start(), and put this in to your Worker Thread function:
private delegate void MyFunctionCaller();
private static void DownloadMap(ParamForDownload p)
{
WebClient client = new WebClient();
client.DownloadFile(p.Url, p.LocalFile);
DownloadMapComplete(p);
}
private void DownloadMapComplete(ParamForDownload p)
{
if (InvokeRequired == true)
{
MyFunctionCaller InvokeCall = delegate { DownloadMapComplete(p); };
Invoke(InvokeCall);
}
else
{
pictureBox2.Picture = p.LocalFile;
}
}

The simplest solution would be to just use the Picturebox.LoadAsync() method and let the Picturebox worry about loading it in the background. If you need to check for errors, use the LoadCompleted event (of the picturebox).
A line like:
pictureBox1.LoadAsync(#"http://imgs.xkcd.com/comics/tech_support_cheat_sheet.png");
is all you need.

When the user initiates the process what you need to do is:
1) Update the UI to indicate something is happening. IE: Disable all the fields and put up a message saying "I am downloading the file, please wait...". Preferentially with some kind of progress indicator (sorry I am not sure if the WebClient supports progress etc but you need to update the UI as the download make take a while).
2) Attach an event handler to the WebClient's 'DownloadFileCompleted'.
3) Use the WebClient's DownloadFileAsync method to start the download on another thread. You don't need to spin threads up yourself.
4) When the 'DownloadFileCompleted' event is fired route the call back to the UI thread by using the form's invoke method. THIS IS VERY IMPORTANT.
See: http://weblogs.asp.net/justin_rogers/pages/126345.aspx
and: http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx
5) Once the event has been routed back onto the UI thread open the file and update the UI as required (IE: Re-enable fields etc).
Leather.

Not related to the threading issue, but if you don't have any other requirements for saving the photo to the disk you can do this:
WebClient client = new WebClient();
byte[] data = client.DownloadData(item.Url);
MemoryStream ms = new MemoryStream(data);
Bitmap img = new Bitmap(ms);
pictureBox.Image = img;

Related

ManualResetEvent wait doesn't release after being set

I'm downloading two JSON files from the webs, after which I want to allow loading two pages, but not before. However, the ManualResetEvent that is required to be set in order to load the page never "fires". Even though I know that it gets set, WaitOne never returns.
Method that launches the downloads:
private void Application_Launching(object sender, LaunchingEventArgs e)
{
PhoneApplicationService.Current.State["doneList"] = new List<int>();
PhoneApplicationService.Current.State["manualResetEvent"] = new ManualResetEvent(false);
Helpers.DownloadAndStoreJsonObject<ArticleList>("http://arkad.tlth.se/api/get_posts/", "articleList");
Helpers.DownloadAndStoreJsonObject<CompanyList>("http://arkad.tlth.se/api/get_posts/?postType=webbkatalog", "catalog");
}
The downloading method, that sets the ManualResetEvent
public static void DownloadAndStoreJsonObject<T>(string url, string objName)
{
var webClient = new WebClient();
webClient.DownloadStringCompleted += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Result))
{
var obj = ProcessJson<T>(e.Result);
PhoneApplicationService.Current.State[objName] = obj;
var doneList = PhoneApplicationService.Current.State["doneList"] as List<int>;
doneList.Add(0);
if (doneList.Count == 2) // Two items loaded
{
(PhoneApplicationService.Current.State["manualResetEvent"] as ManualResetEvent).Set(); // Signal that it's done
}
}
};
webClient.DownloadStringAsync(new Uri(url));
}
The waiting method (constructor in this case)
public SenastePage()
{
InitializeComponent();
if ((PhoneApplicationService.Current.State["doneList"] as List<int>).Count < 2)
{
(PhoneApplicationService.Current.State["manualResetEvent"] as ManualResetEvent).WaitOne();
}
SenasteArticleList.ItemsSource = (PhoneApplicationService.Current.State["articleList"] as ArticleList).posts;
}
If I wait before trying to access that constructor, it easily passes the if-statement and doesn't get caught in the WaitOne, but if I call it immediately, I get stuck, and it never returns...
Any ideas?
Blocking the UI thread must be prevented at all costs. Especially when downloading data: don't forget that your application is executing on a phone, which has a very instable network. If the data takes two minutes to load, then the UI will be freezed for two minutes. It would be an awful user experience.
There's many ways to prevent that. For instance, you can keep the same logic but waiting in a background thread instead of the UI thread:
public SenastePage()
{
// Write the XAML of your page to display the loading animation per default
InitializeComponent();
Task.Factory.StartNew(LoadData);
}
private void LoadData()
{
((ManualResetEvent)PhoneApplicationService.Current.State["manualResetEvent"]).WaitOne();
Dispatcher.BeginInvoke(() =>
{
SenasteArticleList.ItemsSource = ((ArticleList)PhoneApplicationService.Current.State["articleList"]).posts;
// Hide the loading animation
}
}
That's just a quick and dirty way to reach the result you want. You could also rewrite your code using tasks, and using Task.WhenAll to trigger an action when they're all finished.
Perhaps there is a logic problem. In the SenastePage() constructor you are waiting for the set event only if the doneList count is less than two. However, you don't fire the set event until the doneList count is equal to two. You are listening for the set event before it can ever fire.

How should i properly invoke a WebBrowser using multiplethreads?

Problem Scope:
I'm writing an aplication to save the HTML's retrieved from the Bing and Google searches. I know there are classes to execute the Web Requests using stream such as this example, but since Google and Bing both use Javascript and Ajax to render the results into the HTML, there's no way i can simply read the stream and use get to the result i need.
The solution to this, is to use the WebBrowser class and navigate to the url i want, so that the Browser itself will handle all the Javascript and Ajax scripting executions.
MultiThreading:
In order to make it more efficient, i have the same Form aplication firing a thread for each service (one for Bing, and one for Google).
Problem:
Since i need the WebBrowser, i have instantiated one for each thread (which are 2, at this moment). According to Microsoft, there is a known bug that prevents the DocumentCompleted event from firing if the WebBrowser is not visible and is not added to a visible form aswell (for more information, follow this link).
Real Problem:
The main issue is that, the DocumentCompleted event of the browser, never fires. Never.
I have wrote a proper handler for the DocumentCompleted event that never gets the callback. For handling the wait needed for the Browser event to fire, i have implemented a AutoResetEvent with a high timeout (5 minutes), that will dispose the webbrowser thread if it does not fire the event i need after 5 minutes.
At the moment, i have the Browser created and added into a WindowsForm, both are visible, and the event is still not firing.
Some Code:
// Creating Browser Instance
browser = new WebBrowser ();
// Setting up Custom Handler to "Document Completed" Event
browser.DocumentCompleted += DocumentCompletedEvent;
// Setting Up Random Form
genericForm = new Form();
genericForm.Width = 200;
genericForm.Height = 200;
genericForm.Controls.Add (browser);
browser.Visible = true;
As for the Navigation i have the Following (method for the browser) :
public void NavigateTo (string url)
{
CompletedNavigation = false;
if (browser.ReadyState == WebBrowserReadyState.Loading) return;
genericForm.Show (); // Shows the form so that it is visible at the time the browser navigates
browser.Navigate (url);
}
And, for the call of the Navigation i have this :
// Loading URL
browser.NavigateTo(URL);
// Waiting for Our Event To Fire
if (_event.WaitOne (_timeout))
{
// Success
}
{ // Error / Timeout From the AutoResetEvent }
TL:DR:
My WebBrowser is instantiated into a another STAThread, added to a form, both are visible and shown when the Browser Navigation fires, but the DocumentCompleted event from the Browser is never fired, so the AutoResetEvent always times out and i have no response from the browser.
Thanks in Advance and sorry for the long post
Although this seems a strange way, here is my attempt.
var tasks = new Task<string>[]
{
new MyDownloader().Download("http://www.stackoverflow.com"),
new MyDownloader().Download("http://www.google.com")
};
Task.WaitAll(tasks);
Console.WriteLine(tasks[0].Result);
Console.WriteLine(tasks[1].Result);
public class MyDownloader
{
WebBrowser _wb;
TaskCompletionSource<string> _tcs;
ApplicationContext _ctx;
public Task<string> Download(string url)
{
_tcs = new TaskCompletionSource<string>();
var t = new Thread(()=>
{
_wb = new WebBrowser();
_wb.ScriptErrorsSuppressed = true;
_wb.DocumentCompleted += _wb_DocumentCompleted;
_wb.Navigate(url);
_ctx = new ApplicationContext();
Application.Run(_ctx);
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
return _tcs.Task;
}
void _wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
//_tcs.TrySetResult(_wb.DocumentText);
_tcs.TrySetResult(_wb.DocumentTitle);
_ctx.ExitThread();
}
}

How to access & use an object which has been created on a different thread in WPF

I am having following scenario that I need to show preview option in my application like what ms-word does. When we click the info option under File menu item, then preview of document is shown.
In the same way, I also want to show the preview of my data rendering part in my application when someone clicks File\Info panel. For this i have written a method which gets the preview or screenshots of my app but that method is taking some time So when somebody click on the File menu then application hangs for a while. So, i tried to call that method on different thread using background worker as well as normal thread mechanism. but the thing is that method I am calling on different thread it returns an image source object and when I try to access that object on run worker completed event of background worker, then it shows an exception like owner of this object is a different thread which means that returned image has been created on a different thread therefore I can't use it. So, what is the optimized way to get and use that image in my case.
Code tends to be like this.
public void ShowPreview()
{
ImageSource source =null;
var bgWorkerThread = new BackgroundWorker()
bgWorkerThread.DoWork +=(SENDER,ARGS)=> {
source = planView.GetPreviewImage();
}
bgWorkerThread.RunWorkerCompleted += (sender,args)=>
{
// Application crashes at this point
infoPanel.PreviewImage.source = args.Result as ImageSource;
}
}
you can use invoke or you could create a "storage class" (i think its called a singleton but I'm not sure) reuse the same instance across several classes and/or threads like this.
class Test
{
void main()
{
newThread nt = new newThread();
Storage store = new Storage();
nt.store = store;
Thread t = new Thread(new ThreadStart(nt.runMe));
t.Start();
}
}
public class newThread
{
public Storage store;
public void runMe()
{
store.someNum = 8;
}
}
public class Storage
{
public int someNum;
}

C# winform backgroundworker

I am currently working on a home project for myself.
The program is written in C# using winforms.
The problem I'm currently experiencing is as followed:
I have a listview in my mainform called lvwGames
When I run the program without debugging, it runs fine.
However when I start with a debug, I get an error. This has something to do with the background worker thread.
Allow me to post some code to assist me.
private void MainViewLoad(object sender, EventArgs e)
{
RefreshGamesListView();
}
Nothing special here.
The reason I am calling RefreshGamesListView() is because I have to refresh on several occasions.
The method being called looks like this.
public void RefreshGamesListView()
{
pbRefreshGamesList.Value = 0;
bgwRefreshList.RunWorkerAsync();
}
So when the method is called, the background worker is called and runs the dowork method.
This one is quite big.
private void BgwRefreshListDoWork(object sender, DoWorkEventArgs e)
{
List<Game> games = _mainController.RetrieveAllGames();
int count = 1;
foreach (Game game in games)
{
string id = game.id.ToString();
var li = new ListViewItem(id, 0);
li.SubItems.Add(game.title);
li.SubItems.Add(game.Genre.name);
li.SubItems.Add(game.Publisher.name);
li.SubItems.Add(game.Platform.name);
li.SubItems.Add(game.CompletionType.name);
li.SubItems.Add(game.gameNotice);
lvwGames.Items.Add(li);
double dIndex = (double)(count);
double dTotal = (double)games.Count;
double dProgressPercentage = (dIndex / dTotal);
int iProgressPercentage = (int)(dProgressPercentage * 100);
count++;
bgwRefreshList.ReportProgress(iProgressPercentage);
}
}
When i run the code in debug, when the code is on lvwGames.Items.Add(li);
It gives me the following error:
Cross-thread operation not valid: Control 'lvwGames' accessed from a thread other than the thread it was created on.
I have absolutely no clue why.
I think it is code specific. But it can also mean I don't get the background worker completely, and specifically when to use it properly.
The reason I'm using it is because I'm loading a large large list from the database, I want to keep responsiveness in the UI when the list is loaded, and inform the users how far it is, using a progress bar.
If any code is missing, or you actually understand why this is happening PLEASE explain me why in this case its causing the error. You don't need to fix it for me. I just want to know WHY it's caused.
Thanks for taking the time to read this post. I hope to be able to continue using the debugger soon. :)
You need to call Conrol.Invoke when accessing visual controls from background threads.
if (_lvwGames.IsHandleCreated) {
Action addGameToList = () => {
string id = game.id.ToString();
var li = new ListViewItem(id, 0);
li.SubItems.Add(game.title);
....
_lvwGames.Items.Add(li);
};
if (_lvwGames.InvokeRequired) {
_lvwGames.Invoke(addGameToList);
} else {
addGameToList();
}
}
From Manipulating Controls from Threads
...For example, you might call a method that disables a button or
updates a display on a form in response to action taken by a thread.
The .NET Framework provides methods that are safe to call from any
thread for invoking methods that interact with controls owned by other
threads. The Control.Invoke method allows for the synchronous
execution of methods on controls...
This is because you're attempting to access a UI control (lvwGames) from a background thread. The way to make it work requires you to marshal the information back to the main UI thread and update the control from there:
private void BgwRefreshListDoWork(object sender, DoWorkEventArgs e)
{
List<Game> games = _mainController.RetrieveAllGames();
int count = 1;
foreach (Game game in games)
{
string id = game.id.ToString();
var li = new ListViewItem(id, 0);
li.SubItems.Add(game.title);
li.SubItems.Add(game.Genre.name);
li.SubItems.Add(game.Publisher.name);
li.SubItems.Add(game.Platform.name);
li.SubItems.Add(game.CompletionType.name);
li.SubItems.Add(game.gameNotice);
// This is the new line you need:
lvwGames.Invoke(new MethodInvoker(delegate { lvwGames.Items.Add(item) }));
double dIndex = (double)(count);
double dTotal = (double)games.Count;
double dProgressPercentage = (dIndex / dTotal);
int iProgressPercentage = (int)(dProgressPercentage * 100);
count++;
bgwRefreshList.ReportProgress(iProgressPercentage);
}
}
Normally you would check the InvokeRequired property first as mentioned in other answers, but there is really no need if you are always calling it from the background thread. Your DoWork method will always require an invoke call, so you might as well just go ahead and write it like that.
This happening cause, just like compiler cliams, you are going to update UI control content from another thread. You can not do that, as UI control can be updated only within main thread.
Please have look on this SO answer with example code provided:
Invoke from another thread
The background worker is not working properly if you run in debug mode in studio. If you have calls that use the windows handle to retrieve messages, then they will fail. If you for instance have a progressChanged event handler and this changes a text in a textbox that might fail.
I had this scenario: A Form that has a background worker. If I just start the worker without getting a dialog box up first then it works ok. If I show a dialog and then start the background worker then it fails. When I run the program normally it does not fail. It is somehow the debug environment that destroys the link between the events and the foreground window. I have changed my code to use invoke, and now all works both in when running in release and when I debug.
Here is a link explaining what can be done to make a program thread safe.
http://msdn.microsoft.com/en-us/library/ms171728(VS.80).aspx
I did not do the same as the sample to microsoft. I made delegates, assigned to the functions I needed to run. and called invoke on them.
sample pseudo code:
class MyClassWithDelegates
{
public delegate void ProgressDelegate( int progress );
public ProgressDelegate myProgress;
public void MyProgress(int progress)
{
myTextbox.Text = ..... ; // this is code that must be run in the GUI thread.
}
public MyClassWithDelegates()
{
myProgress = new ProgressDelegate(MyProgress);
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Invoke( myProgress, e.ProgressPercentage );
}
}
All code that potentially have to be run in the GUI thread of the application must be Invoked to be safe.

WPF and backgroundworker problem

In my program there is a BackgroundWorker class that preloads the images to BitmapImage object. I need to pass that preloaded image to the main application(WPF), where it will be copied to another BitmapImage object. This seems to be working, however, when i try
imgViewer.Source = imgNext; //imgNext is a main app copy of the preloaded image
an error occurs meaning that that object(imgNext) is owned by another thread and it cannot be used.
Any ideas how to get rid of it and get the code working?
Thanks everybody for answering!
In fact, I managed to solve this problem by creating a static BitmapImage inside App class.
Before using it, I do
App.iNext = null;
Then I load the actual image and freeze it, so this static property can be accessed from everywhere. When the cycle repeats many times, assigning null prevents 'object is frozen' errors.
And of course, there was a lot of work with managing single BGW instance, queuing tasks etc.
(Currently I'm using ImagesContainer class defined also in my program that has two BitmapImage properties. I use it to receive preloaded images from backgroundworker. )
imgNext is a public variable defined in MainWindow. (main thread)
void bwImgLoader_DoWork(object sender, DoWorkEventArgs e)
{
backgrLoadNextPrevList list = e.Argument as backgrLoadNextPrevList;
ImagesContainer result = new ImagesContainer();
if (list.HasNextPath) result.imgPrev = PrepareImage(list.NextPath);
if (list.HasPrevPath) result.imgNext = PrepareImage(list.PrevPath);
e.Result = result;
}
void bwImgLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ImagesContainer result = e.Result as ImagesContainer;
if (result.imgNext != null)
{
setNextDelegate s = new setNextDelegate(setNext);
object[] t = { result.imgNext };
imgNext.Dispatcher.Invoke(s, t);
}
// do not take into account this line, just ignore it.
//if (result.imgPrev != null) imgPrev = result.imgPrev;
}
public void setNext(BitmapImage b)
{
imgNext = b;
}
public delegate void setNextDelegate(BitmapImage b);
Freezing the bitmapimage helps only on the first background load(see the comment under the answer below). When I call BackgroundWorker second time, an errors occures that the object is frozen and cannot be modified. Is there a way to un-freeze it?
Or, is there any way to copy data from one thread to another without copying an attribution to thread?
UPDATED
Thanks everybody for answering!
In fact, I managed to solve this problem by creating a static BitmapImage inside App class.
Before using it, I do
App.iNext = null;
Then I load the actual image and freeze it, so this static property can be accessed from everywhere. When the cycle repeats many times, assigning null prevents errors.
And of course, there was a lot of work with managing single BGW instance, queuing tasks etc.
But these efforts were worth the result - I got +125% in performance!!! Thank everyone!
BitmapImage is Freezable so you can Freeze() it after loading it. This will permit access from any thread.
It's easiest to create all UI objects on the same thread. This includes any classes descending from DispatcherObject, such as BitmapImage.
On the UI thread - before creating the BGW - capture the result of TaskScheduler.FromCurrentSynchronizationContext. You can stick it in a private member of your class. e.g.:
private TaskScheduler ui;
public void InitiateBGW()
{
this.ui = TaskScheduler.FromCurrentSynchronizationContext();
this.bwImgLoader.RunWorkerAsync();
}
On the BGW, whenever you need to access BitmapImage functionality (creating them or modifying them), queue it to the TaskScheduler like this:
private BitmapImage PrepareImage(string path)
{
// This code runs in a BGW.
// Load underlying bitmap (non-UI)...
var bitmap = ..;
// Prepare the bitmap (non-UI)...
return Task.Factory.StartNew(() =>
{
var ret = new BitmapImage();
// Load "bitmap" into "ret"
return ret;
}, CancellationToken.None, TaskCreationOptions.None, this.ui);
}

Categories