Thread makes application halt - c#

I'm currently trying to create a FileViewer control, and after I've added Items (Filenames with Icons, size, etc) to my ListView (Icon - Filename - Extension - Size) I also check if the file is an image (png/jpg, etc) but this I do on a different Thread.
My expectation of a thread is that it runs beside the main application, but after I've added all my files I start this thread. It checks all files in the ListView and creates thumbnails for them. If done correctly, ListView icons should appear one after one as they're loaded - but they're not. They all appear at the same time.
...and I can't do anything while the Thread is active.
Why is this happening and what am I doing wrong? I've dealt with Threads before and it's always worked, I invoke the method with a Callback.
Flow of the Thread:
Format file key = "C:\image.png" = "C_image_png".
Check if thumbnail to image exists (by checking it's key), then use it
Else load thumbnail with Image.FromFile().GetThumbnailImage() and add image with Key to Listview's images
Finally change the ImageKey of the ListView item.
All done in a thread.
private void GetFiles()
{
// Load all files in directory
Thread t = new Thread(new ThreadStart(GetImageFiles));
t.Priority = ThreadPriority.Lowest;
t.Start();
}
delegate void GetImageFilesCallback();
private void GetImageFiles()
{
if (this.IsHandleCreated)
{
if (files.InvokeRequired)
{
GetImageFilesCallback callback = new GetImageFilesCallback(GetImageFiles);
this.Invoke(callback);
}
else
{
string extension = "";
string key = "";
foreach (string file in _files)
{
extension = FileManager.GetExtension(file);
key = (DirectoryCurrent + file).Replace(":", "").Replace("\\", "_").Replace(".", "_");
foreach (string knownimages in _knownImageTypes)
{
if (extension.ToLower() == knownimages)
{
foreach (ListViewItem item in files.Items)
{
if (item.Text == file)
{
if (files.SmallImageList != null)
{
if (files.SmallImageList.Images[key] == null)
{
files.SmallImageList.Images.Add(key, Image.FromFile(DirectoryCurrent + file).GetThumbnailImage(16, 16, null, IntPtr.Zero));
files.LargeImageList.Images.Add(key, Image.FromFile(DirectoryCurrent + file).GetThumbnailImage(32, 32, null, IntPtr.Zero));
}
files.Items[item.Index].ImageKey = key;
}
}
}
}
}
}
files.Refresh();
}
}
}

The method that your thread calls is invoking itself onto the main thread, and then doing all the work in that thread, thereby blocking your UI.
You should arrange your code so that the thread code does not touch the ListView, but just loads each image, then invokes a main-thread method, passing the bitmaps so that the main thread can assign them to the ListView.
Here's a sketch of what I mean:
// this is your thread method
// it touches no UI elements, just loads files and passes them to the main thread
private void LoadFiles(List<string> filenames) {
foreach (var file in filenames) {
var key = filename.Replace(...);
var largeBmp = Image.FromFile(...);
var smallBmp = Image.FromFile(...);
this.Invoke(new AddImagesDelegate(AddImages), key, largeBmp, smallBmp);
}
}
// this executes on the main (UI) thread
private void AddImages(string key, Bitmap large, Bitmap small) {
// add bitmaps to listview
files.SmallImageList.Images.Add(key, small);
files.LargeImageList.Images.Add(key, large);
}
private delegate AddImagesDelegate(string key, Bitmap large, Bitmap small);

Read the following: http://www.codeproject.com/Articles/10311/What-s-up-with-BeginInvoke. The important thing with Invoke and BeginInvoke is that they both operate on the Main thread. BeginInvoke just doesn't wait for the message to be processed before returning control. Eventually though, the work will happen on the Main thread and will block until it is complete.

Related

C#: Window hangs when I try to move it, click outside or minimize

This the picture of the application that I am trying to run:
when I try to click outside, move it, or minimize it, the window hangs:
my code:
public void Process()
{
// using (HalconDotNet.HWindowControl HWinCtrl = new HalconDotNet.HWindowControl())
// {
using (PitFinderEngine VisionEngine = new PitFinderEngine())
{
foreach (string jpegfile in JpegFiles)
{
try
{
string[] parts1 = jpegfile.Split('\\');
string[] parts2 = parts1[parts1.Length - 1].Split('.')[0].Split('_');
string row = parts2[parts2.Length - 2].Split('R')[1];
string col = parts2[parts2.Length - 1].Split('C')[1];
Results.Add(row + " " + col, VisionEngine.action(jpegfile, "Production"));
//FormControls.ProgressBar_DoStep();
}
catch (Exception e)
{
}
}
}
}
the "Process" is called in this manner:
public static Thread StartTheThread(string LN, string TN)
{
var t = new Thread(() => RealStart(LN, TN));
t.Start();
return t;
}
private static void RealStart(string ln, string tn) {
Lots[ln].Tiles[tn].Process();
}
public static void DoWork()
{
string[] Directories = Directory.GetDirectories(LotPictureFolder);
List<Thread> ThreadList = new List<Thread>();
foreach (string lot in Directories)
{
string[] parts = lot.Split('\\');
string LotName = parts[parts.Length - 1];
foreach (string tile in Lots[LotName].Tiles.Keys)
{
if (!Lots[LotName].Tiles[tile].VicFileFound)
{
ThreadList.Add(StartTheThread(LotName, tile));
}
}
}
bool AllDone = false;
while (!AllDone)
{
AllDone = true;
foreach (Thread th in ThreadList)
{
if (th.IsAlive)
{
AllDone = false;
}
}
}
}
it seems that
VisionEngine.action(jpegfile, "Production")
takes 10ms - 20ms to run and is responsible to hanging the window, if i were to move the window; meaning, that if i were to comment it off the problem will not be there, is it possible to isolate this code, I tried using threads, but the issue still persists, i cant use tasks as the platform is .Net3.5.
The problem is that when you run longer tasks for you application it blocks the UI until the order is done. That's because your task blocks the MainThread for this order, so the UI can't respond, since it's running in the MainThread as well. Because the UI does not respond to Windows it's telling you: Hey, watchout this window does not respond to any user actions currently.
Even though your application does a lot of stuff in the background (what you assigned your application to do), but this does not matter for the user and Windows. The problem is that once you click more on your application, the blocked UI, Windows will still notice that and suggest you to close the window, because it seems like the application is stuck.
Simply use Application.DoEvents() in your long running task. This behaves as the following:
Calling this method causes the current thread to be suspended while all waiting window messages are processed.
So it pauses your currently long running task, to process the Windows messages which came from e.g. the user input, so maximizing, moving, ... the window. After that it will continue working on your long running order.
Application.DoEvents();
This implies that if e.g. your method takes 10s to complete, calling Application.DoEvents() once makes your application process user actions only one time, what does not seem like a behaviour you want. So you have to call it multiple times. Note that this can make your running method significantly slower.
You don't seem to use a ThreadList at this point anymore. Your whole process method should start a new thread. Then make sure with events you modify the ProgressBar it's progress. Once all of that code runs on a seperate thread your UI shouldn't freeze because of that anymore.

How do I update a progress dialog?

I've read about Invoke(ing) controls and whatnot... I don't know WHAT controls I'm supposed to invoke as both my main form and dialog form have more than one, hence my question here. I've read this and this and this ... I simply don't understand how to apply it to my situation. Is there a tutorial somewhere that I go read to try to understand better?
I have a winform app (C#) that does some work. Some of this work may take a while so I thought I'd provide a progress dialog of sorts to alert the user that activity is taking place (and not simply relying on a list control flashing periodically to indicate something updated).
So, I added a new form to my project, added a few items of interest (# of items to process, estimated completion time, current item and an overall progress bar).
public ProgressDialog Progress { get; set; }
public Form1()
{
Progress = new ProgressDialog(this);
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
}
I set the main work to be done in a backgroundworker once the Process button is clicked.
private void buttonProcess_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy != true)
{
backgroundWorker1.RunWorkerAsync();
Progress.ShowDialog();
}
}
From a method that is called on that thread, I call a method on my ProgressDialog form:
Progress.UpdateDialog(numFiles: filesToProcess.Count,
processTime: TimeSpan.FromTicks(filesToProcess.Count * TimeSpan.TicksPerSecond * 20)); // 20s is an estimated time, it will be updated after first file is processed.
The code in question there (in ProgressDialog.cs):
public void UpdateDialog(int percentComplete = 0, string fileName = "", int numFiles = 0, TimeSpan? processTime = null)
{
...
if (numFiles > 0)
{
labelNumFiles.Text = Convert.ToString(numFiles);
}
if (!processTime.Equals(null))
{
labelProcessTime.Text = Convert.ToString(processTime);
}
}
Which results in the following error:
An exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll but was not handled in user code
Additional information: Cross-thread operation not valid: Control 'ProgressDialog' accessed from a thread other than the thread it was created on.
Additionally, the main form has two list controls that need to be updated as the files are processed: a processed file list and an error file list. Had things working fine until I got the brilliant idea to add the progress dialog. LOL So, what is the proper way to handle updating a progress dialog?
The UpdateDialog method needs to be run on the UI thread since it is doing UI work. Since it is being called during the DoWork of your BackgroundWorker (which is most likely not running on your UI thread), you need to do use the InvokeRequired/Invoke pattern for Winforms:
public void UpdateDialog(int percentComplete = 0, string fileName = "", int numFiles = 0, TimeSpan? processTime = null)
{
if (InvokeRequired)
{
Invoke(new System.Windows.Forms.MethodInvoker(() => UpdateDialog(percentComplete, fileName, numFiles, processTime)));
}
...
if (numFiles > 0)
{
labelNumFiles.Text = Convert.ToString(numFiles);
}
if (!processTime.Equals(null))
{
labelProcessTime.Text = Convert.ToString(processTime);
}
}
You have to use Invoke() method to access at user control form a tread different form the one that have created the thread
see this or this for a better trattation of the argument

Windows 8 - How to get nested folder properly?

I have a GridView that's bound to a collection of objects which load images from the disk.
The objects are put onto a stack when they become visible, and the images are loaded off the stack sequentially.
The problem is that GetFolderAsync() doesn't return until the ScrollViewer containing the objects has stopped scrolling.
The code is as below:
public static async Task<StorageFolder> GetFileFolderAsync(String fileUrl)
{
try
{
string filePathRelative = DownloadedFilePaths.GetRelativeFilePathFromUrl(fileUrl);
string[] words = filePathRelative.Split('\\');
StorageFolder currentFolder = await DownloadedFilePaths.GetAppDownloadsFolder();
for (int i = 0; (i < words.Length - 1); i++)
{
//this is where it "waits" for the scroll viewer to slow down/stop
currentFolder = await currentFolder.GetFolderAsync(words[i]);
}
return currentFolder;
}
catch (Exception)
{
return null;
}
}
I've pinpointed it down to that line where it gets the folder that contains the image. Is this even the proper way to get a nested folder?
You could try to use ConfigureAwait(false) to run the for loop on a thread pool thread:
public static async Task<StorageFolder> GetFileFolderAsync(String fileUrl)
{
try
{
string filePathRelative = DownloadedFilePaths.GetRelativeFilePathFromUrl(fileUrl);
string[] words = filePathRelative.Split('\\');
// HERE added ConfigureAwait call
StorageFolder currentFolder = await
DownloadedFilePaths.GetAppDownloadsFolder().ConfigureAwait(false);
// Code that follows ConfigureAwait(false) call will (usually) be
// scheduled on a background (non-UI) thread.
for (int i = 0; (i < words.Length - 1); i++)
{
// should no longer be on the UI thread,
// so scrollviewer will no longer block
currentFolder = await currentFolder.GetFolderAsync(words[i]);
}
return currentFolder;
}
catch (Exception)
{
return null;
}
}
Note that in the above case since there is no work that is done on the UI, you CAN use ConfigureAwait(false). For example, the following would not work because there is a UI related call after the ConfigureAwait:
// HERE added ConfigureAwait call
StorageFolder currentFolder = await
DownloadedFilePaths.GetAppDownloadsFolder().ConfigureAwait(false);
// Can fail because execution is possibly not on UI thread anymore:
myTextBox.Text = currentFolder.Path;
It turns out the method I was using to determine the object's visibility was blocking the UI thread.
I have a GridView that's bound to a collection of objects which load images from the disk.
The objects are put onto a stack when they become visible, and the images are loaded off the stack sequentially.
The problem is that GetFolderAsync() doesn't return until the ScrollViewer containing the objects has stopped scrolling.

In C#, how do you lock a form?

I'm trying to lock the main form while a please wait box is shown on the screen, but it won't work. Here's my dilemma.
I have 2 forms. The main form that the user clicks a refresh button to load the list of SQL Servers, and a Please wait form that shows while it's loading the list. The SQL Server thread is a separate thread by default while using C# and it locks out the main thread in order to process the SQL request.
I can add a background worker, but then I can't update my combo box to show the list as its a UI control. If I use a handler for that, my show_dialog() for the please wait box will stop locking down the main form.
How is it even possible to lock this form down without the left click queue being run after the main thread goes active again? I added the code that needs to be executed while the user waits.
public void PullServers()
{
bool ServersFound = false;
foreach (string Value in SQL.LocateSqlInstances())
{
this.cmbServer.Items.Add(Value);
ServersFound = true;
}
if (!ServersFound)
{
this.cmbServer.Items.Add(Strings.Lang("ddServerNoneFound"));
this.cmbServer.SelectedIndex = 0;
}
else
{
if (!s.empty(General.setting("SQLSERVER")))
{
this.cmbServer.Text = General.setting("SQLSERVER");
}
else
{
this.cmbServer.SelectedIndex = 0;
}
}
this.picRefreshServers.Image = Properties.Resources.Refresh;
}
public static Array LocateSqlInstances()
{
using (DataTable sqlSources = System.Data.Sql.SqlDataSourceEnumerator.Instance.GetDataSources())
{
string Servers = null;
foreach (DataRow source in sqlSources.Rows)
{
string instanceName = source["InstanceName"].ToString();
if (!s.empty(instanceName))
{
Servers += source["ServerName"].ToString() + "\\" + instanceName + "[[SERVBREAK]]";
}
}
string[] ServersList = Servers.Split(new string[] { "[[SERVBREAK]]" }, StringSplitOptions.RemoveEmptyEntries);
return ServersList;
}
}
I think you are on the right track with a BackgroundWorker. I have found the following pattern to work well for me.
In your main form, you need to perform the following steps.
Create a BackgroundWorker to perform the long running operation.
Start the BackgroundWorker.
Display the waiting form as a modal dialog.
// Step 1:
BackgroundWorker bg = new BackgroundWorker()
bg.DoWork += new DoWorkEventHandler(bg_DoWork);
bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_RunWorkerCompleted);
// Step 2:
bg.RunWorkerAsync();
// Step 3:
waitingForm = new WaitingForm();
waitingForm.ShowDialog();
As you know, you can't update the UI from the bg_DoWork handler since it does not run on the UI thread. So just get the data you need here and pass it on to the the bg_RunWorkerCompleted handler using the e.Result parameter.
private void bg_DoWork(object sender, DoWorkEventArgs e)
{
Array servers = SQL.LocateSqlInstances();
e.Result = servers;
}
The bg_RunWorkerCompleted runs on the UI thread so it is safe to update your controls here. This is where you should close the waiting form and then update your UI.
private void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Close the Waiting form.
waitingForm.Close();
// Retrieve the result of bg_DoWork().
Array servers = e.Result as Array;
bool ServersFound = false;
foreach (string Value in servers)
{
this.cmbServer.Items.Add(Value);
ServersFound = true;
}
if (!ServersFound)
{
this.cmbServer.Items.Add(Strings.Lang("ddServerNoneFound"));
this.cmbServer.SelectedIndex = 0;
}
else
{
if (!s.empty(General.setting("SQLSERVER")))
{
this.cmbServer.Text = General.setting("SQLSERVER");
}
else
{
this.cmbServer.SelectedIndex = 0;
}
}
this.picRefreshServers.Image = Properties.Resources.Refresh;
}

C# thread-safe calls on dynamically created controls

I know how to perform thread-safe calls on user controls, but when they are dynamically generated I don't have a clue. I tried to make an array list of them (rich text boxes) and I had problems with the 'parameter start thread' delegate after that. You input is much appreciated.
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.currentBox.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke( d, new object[] { text } );
}
else
{
this.currentBox.Text += text;
}
}
// This method is executed on the worker thread and makes
// a thread-safe call on the TextBox control. On a specific text box.....
private void ThreadProcSafe()
{
int i = 0;
while(_stop_threads == false) {
this.SetText(String.Format("{0}\n", i));
++i;
Thread.Sleep(100);
}
this.SetText(String.Format("Thread Time: {0}:{1}:{2}\n", DateTime.Now.TimeOfDay.Hours, DateTime.Now.TimeOfDay.Minutes, DateTime.Now.TimeOfDay.Seconds));
}
The problem is, although you have a separate thread for each UI control, that windows form works under a single thread. So, it is not safe updating UI controls from separate threads.
Maybe you may fill up a dictionary or array from those threads and update the results from that array under the Form's single thread. This way, you will be updating all the UI controls same time from the updated array of values.It is thread safe and it consumes less system resources.

Categories

Resources