1

Background

Currently working on a windows form app which I asked to create. I have ran into an issue where the UI freezes when a resource intensive process is being called. I am currently using threading from which I understand is used to prevent the UI from freezing and taking over the entire pc.

Question

Currently when I am using threading to call a method in my base class which is to open a file that is located on a remote server. This method has a delay of approximately 30 to 45 seconds. I am creating my background thread and invoking it to start. When invoked to start if fires, however when it fired it would not wait for my thread to complete basically giving me a null exception. So after some digging I found that in order to wait for the thread to complete you had to invoke the .Join(). However when the Join is invoked it froze my UI completely. So my ingenuity tried to create a work around and created a while loop that would until the thread is no longer alive and continue. However, this also froze the UI. So am I missing something? That is not mention in MSDN Doc

Code Sample

class BaseClass
{
    public CWClient ClientFileContext(string clientFile, bool compress)
    {
        Client clientContext = null;
        try
        {
            if (compress == true)
            {
                clientContext = appInstance.Clients.Open2(clientFile, superUser, passWord, OpenFlags.ofCompressed);
            }
            else
            {
                clientContext = appInstance.Clients.Open2(clientFile, superUser, passWord, OpenFlags.ofNone);
            }
        }
        catch (Exception ex)
        {
            //TODO
        }
        return clientContext;
    }
}

  public partial class Form1 : Form
  {
   private void button1_Click(object sender, EventArgs e) 
    {
        BaseClass wpSec = new BaseClass();
        CWClient client = null;               

        Thread backgroundThread = new Thread(
            new ThreadStart(() =>
            {
                client = wpSec.ClientFileContext(selectedFileFullPath, true);
            }
        ));
        backgroundThread.Start();
        //backgroundThread.Join(); << Freezes the UI
        var whyAreYouNotWorking = "Stop";
    }
  }

Work around I tried

 while (backgroundThread.IsAlive == true)
 {
    for (int n = 0; n < 100; n++)
    {
       Thread.Sleep(500);
       progressBar1.BeginInvoke(new Action(() => progressBar1.Value = n));
    }
 }
  // This also freezes the UI
EasyE
  • 560
  • 5
  • 26
  • 1
    Yes, the Join (immediately after Start) negates the whole Threading setup. What did you expect? It will also deadlock any Control.Invoke() calls. – H H Jul 26 '17 at 17:37
  • 1
    The easy answer here is to use a BackgroundWorker – H H Jul 26 '17 at 17:37
  • So you know that if you block your UI thread and force it to do nothing until your background work finishes, your UI is frozen, and when you don't, it doesn't. So...what's your question. You already know how to fix your problem, *don't block the UI thread*. You've already shown the code to not block the UI thread yourself. – Servy Jul 26 '17 at 17:37
  • @Servy Correct I am not blocking the UI however I still need to load the results from the thread that I start. – EasyE Jul 26 '17 at 17:41
  • 1
    @EasyE Do that from the background thread, rather than stopping the UI thread from being able to do anything until the background thread finishes. – Servy Jul 26 '17 at 17:46
  • From a concept perspective: The UI thread should never have any type of wait/sleep/join. UI thread is already in an infinite loop checking the message queue and the sleep/wait/join freezes that loop. So the way to check for any status (or completion of a parallel task) is via that message queue. One way is for the worker thread to raise an event and the subscriber will get control in the UI thread. – Vikhram Jul 26 '17 at 18:03
  • Could you please do a better job explaining what the desired behavior is? You've rightly determined that Thread.Join is the cause of the issue, but where should Thread.Join go? What outcome are you expecting (aside from the UI not being frozen)? What is supposed to happen when? – theMayer Jul 26 '17 at 18:11
  • @theMayer I apologize for the scattered question my overall objective is to call the the function in my base class and get the results. – EasyE Jul 26 '17 at 18:17
  • Is getting the results enough? Do you intend to display those results in the UI, or is there some other method you are using to see what happened? – theMayer Jul 26 '17 at 18:59
  • @theMayer Well for this question the results is actually what I am trying to acquire with out Freezing the UI. However, my final product will display all the results in a tree view. – EasyE Jul 26 '17 at 19:06
  • @HenkHolterman Thanks – EasyE Jul 27 '17 at 16:30
  • Here is the answer https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls?view=netframeworkdesktop-4.8 – NoWar Apr 20 '21 at 03:34

1 Answers1

3

I would also look into the async and await pattern for this. Explained in this post: Using async await still freezes GUI

Your code should be similar to this (Baseclass doesn't change) :

public partial class Form1 : Form
{
   private async void button1_Click(object sender, EventArgs e) 
   {
      BaseClass wpSec = new BaseClass();
      CWClient client = await Task.Run(() =>
          {
              return wpSec.ClientFileContext(selectedFileFullPath, true);
          }
      );
      var whyAreYouNotWorking = "Stop";
   }
}

This is back-of-the-envelope stuff, but hopefully that gives the basic idea of launching a task, then awaiting the result in an async method. If you don't need your BaseClass hanging around, that can be in the lambda too, leaving you only what you really want.

That link from @Chris Dunaway above is also excellent. http://blog.stephencleary.com/2013/08/taskrun-vs-backgroundworker-round-3.html

Edit: As @BradlyUffner mentions, this is also one of the few times you should use async void and should rather prefer returning Task or Task<T> in virtually all other circumstances.

Kevin Anderson
  • 6,850
  • 4
  • 32
  • 54
  • To people reading this answer, please note that this shows one of the very few *valid* places where `async void` should be used. Anything else that is `async` should return `Task` or `Task`, unless you understand `async` *very* well, and know exactly what you are doing, – Bradley Uffner Jul 26 '17 at 18:12
  • From what I was reading I took a different approach and made my function in the base class an async task. Is there a benefit doing it your way ? I will update my code example. – EasyE Jul 26 '17 at 18:12
  • Fair point @BradleyUffner, as I agree, usually you should be doing as you say. I'll edit and mention that. – Kevin Anderson Jul 26 '17 at 19:11