Deukalion
Deukalion

Reputation: 2655

Thread makes application halt

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:

  1. Format file key = "C:\image.png" = "C_image_png".
  2. Check if thumbnail to image exists (by checking it's key), then use it
  3. Else load thumbnail with Image.FromFile().GetThumbnailImage() and add image with Key to Listview's images
  4. 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();
            }
        }
    }

Upvotes: 0

Views: 141

Answers (2)

pixelbadger
pixelbadger

Reputation: 1596

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.

Upvotes: 0

Blorgbeard
Blorgbeard

Reputation: 103447

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);

Upvotes: 1

Related Questions