1

Let's say I have a FormWithLabel (just form with a label to show status of current long-term operation) which I show in case of some "heavy" tasks running in my main application form. For this purpose I have static variable:

public static FormWithLabel loadingForm = null;

I case of "heavy" tasks I create FormWithLabel in separate thread an show it until log operation ends. When I use Thread - it's ok:

Thread loadingFormThread = new Thread(new ThreadStart(() =>
    {
        loadingForm = new FormWithLabel();
        loadingForm.ShowDialog();
    }
    ));

    loadingFormThread.SetApartmentState(ApartmentState.STA);
    loadingFormThread.Start();
...
//some "heavy" work which is done separate thread and updates some visual data in main UI via Invoke()
...
if (loadingForm != null)
    {
        loadingForm.Dispose();
        loadingForm = null;
    }

But when I use Task instead of Thread

new Task(() =>
    {
         loadingForm = new FormWithLabel();
         loadingForm.ShowDialog();
    }).Start();
...
//some "heavy" work which is done separate thread and updates some visual data in main UI via Invoke()
...
if (loadingForm != null)
    {
        loadingForm.Dispose();
        loadingForm = null;
    }

loadingForm is NULL at the end of "heavy" work - so .Dispose() is never called.

What is the difference between Thread and Task?

Why my static variable remain global in first case and look like it's local for Taks's thread in second case?

9
  • 8
    You are breaking just about every rule in the book. The task just makes it worse, it is not STA. Only ever do this if you understand how to do this. Do this the correct way, the "heavy work" must be done on a worker thread, always keep the UI thread available to display and update UI. Commented Oct 14, 2016 at 9:55
  • Most part of the approach witt Thread was copied from that Telerik sample Commented Oct 14, 2016 at 11:59
  • 2
    Splashscreens are normally pretty innocent, they just display a bitmap and don't use controls that use the SystemEvents class. Like Label. Just arbitrarily omitting the Invoke() call that closes the window is also a major mistake. Threading bugs like this are very hard to debug. Don't do it. Commented Oct 14, 2016 at 12:14
  • Ok, let's imagine my FormWithLabel has nothing but a picturebox in it. What does it change in behavoir described in my question? Threads still work correctly, but Tasks - not.. And BTW - where do you see Invoke() call in my code sample above? Commented Oct 14, 2016 at 13:06
  • 1
    Better way would be to create your FormWithLabel from main UI thread (no task or thread) and then use task or thread to do the heavy work and may be use Invoke to update UI from your heavy work. Commented Oct 14, 2016 at 13:16

3 Answers 3

1

The issue with loadingForm being null at Dispose it might be caused by fact that variable is not marked as volatile, but it is used from more threads. In which case compiler or runtime might decide do optimization (cache) of variable.

Sign up to request clarification or add additional context in comments.

3 Comments

loadingForm static variable is used from Task and than from you main thread, that is were you create and started the task and you do Dispose of loadingForm. Or maybe I just don't understand you fragment of code, so may be you could improve it?
I mean that loadingForm is not used from threads other that two described by you (main UI thread and Task's thread)
So you using it from more than one thread and that is causing your issue. It is also consistent with your discovery that if you change it into property behavior changes as it depends on complex optimizations. As mentioned earlier, consider redesign of your code.
0

So I followed Honza's advice (creating FormWithLabel from main UI thread) and redesigned my code as follows:

public MainForm()
    {
        InitializeComponent();
        FormWithLabel splash = new FormWithLabel();
        //start long-term work in a separate thread
        new Task(() => { DoHeavyWork(splash); }).Start();
        //FormWithLabel will be closed at the end of DoHeavyWork task
        splash.ShowDialog(this);
    }

private void DoHeavyWork(FormWthLabel splash)
    {
        for (int i = 1; i <= 5; i++)
        {
            //checking whether FormWithHelp handle is already created to ensure 
            //that following Invoke will not throw InvalidOperationException
            while (!lScreen.IsHandleCreated)
                Thread.Sleep(50);
            //changing text inside a label on FormWithLabel (assuming label is public)
            splash.Invoke(new Action(() => { splash.label.Text = "Loading item №" + i; }));   
            //some heavy work emulation
            Thread.Sleep(1000);                             
        }
        //closing FormWithLabel and continuing main thread (showing fully filled main form)
        splash.Invoke(new Action(splash.Close));
    }

Comments

0

Tasks is used when we donot want the application thread to lag or slow down due to intensive tasks. Like in a javafx application we cannot use a thread directly we have to use task if we want to keep the ui free from lag and slowing down. And if you want to complete any task after thread has executed you have to use a listner like taskOnComplete or OnSucced. This is the reason dispose is not getting called

For multiple tasks in parallel you can use Executor service.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.