locked
Task.ContinueWith() hangs RRS feed

  • Question

  • Hi, let me ask what am I doing wrong here...

    I use Task.Factory.StartNew... for creating task for lets say long running task (querying data from DB and assigning them to my fields) and then pass the result of task to ContinueWith()... method that updates my UI and on top of it all I wait for whole thing to be done with method Wait(). With the code below I never run to ContinueWith task and application hangs there. If I wait first for the task one to finish and then append my ContinueWith method it works fine but the applcation UI is unresponsive for that moment until task finishes.

    Task<object[]>.Factory.StartNew(o =>
                    {
                        object[] obj = o as object[];
                        MySQL_DB db = (obj[0] as MySQL_DB);
                        Model m = (obj[1] as Model);
                        m.Model_ID = db.Get_models_ModelID(m.Model_name);
                        m.Model_number = db.Get_models_ModelNo(Convert.ToUInt16(m.Model_ID));
                        m.Model_MI_ID = db.Get_chipmount_ModelID(Convert.ToUInt16(m.Model_ID));
                        m.Model_pattern_count = Convert.ToByte(db.Get_chipmount_ModelPatternCount(m.Model_MI_ID));
                        object[] data = new object[] { m, (db.Get_ecs_last_module_SN(Convert.ToUInt16(m.Model_ID)) + 1), db.Get_GeneratedButNotUsedModulesCount((UInt16)m.Model_MI_ID) };
                        return data;
                    }, new object[] { this.Database_MySQL, Model }, TaskCreationOptions.LongRunning).ContinueWith((res) =>
                        {                        
                            Model = (res.Result as object[])[0] as Model;
                            this.lblModelID.Text = Model.Model_ID.ToString("D5");
                            this.lblModelFullName.Text = Model.Model_name;
                            this.lblModelNo.Text = Model.Model_number;
                            PatternCount = Model.Model_pattern_count;
                            this.lblPatternCount.Text = PatternCount.ToString();
                            this.numUpDownModulesCount.Increment = PatternCount;
                            this.numUpDownSNfrom.Increment = PatternCount;
                            this.numUpDownSNto.Increment = PatternCount;
                            this.numUpDownModulesCount.Maximum = this.numUpDownSheetCount.Maximum * PatternCount;
                            FirstSn = Convert.ToUInt32((res.Result as object[])[1]);
                            this.numUpDownSNto.Maximum = FirstSn + (this.numUpDownSheetCount.Maximum * PatternCount) - 1;
                            this.lblActualFreeModulesCount.Text = (res.Result as object[])[2].ToString();
                        }, TaskScheduler.FromCurrentSynchronizationContext()).Wait();

    What I found out the code in ContinueWith run as well if I ommit TaskScheduler.FromCurrentSynchronizationContext() but it throws error, because I update UI from within other thread, obviously.

    Please can anyone solve the issue? I read almost whole MSDN :) and i know that it should work that way (i got a similar app that uses TPL that way and it works fine) but somehow thisone does not...

    Friday, July 3, 2015 11:03 AM

Answers

  • That Wait does not belong there. Not only that it blocks the UI thread and defeats the purpose of running the query code on a background thread but it also leads to a deadlock - the UI thread is stuck in Wait and TaskSchedulers.FromCurrentSynchronizationContext() tries to tell the UI thread to do something. The UI thread can't do it and the wait will never complete.

    Why do you need that Wait? If you need to prevent the user from doing something in the UI consider disabling the relevant UI controls or displaying a modal dialog.

    • Marked as answer by fimu1 Friday, July 3, 2015 12:57 PM
    Friday, July 3, 2015 12:12 PM

All replies

  • Updating the UI from any place other then the original UI thread can cause issues, wich is why it is forbidden since .NET 2.0

    The proper way to do UI updates is to use invoke to put the change orders into the Event Queue of said UI Thread.

    http://stackoverflow.com/questions/14703698/invokedelegate/14703806#14703806

    Friday, July 3, 2015 11:12 AM
  • That Wait does not belong there. Not only that it blocks the UI thread and defeats the purpose of running the query code on a background thread but it also leads to a deadlock - the UI thread is stuck in Wait and TaskSchedulers.FromCurrentSynchronizationContext() tries to tell the UI thread to do something. The UI thread can't do it and the wait will never complete.

    Why do you need that Wait? If you need to prevent the user from doing something in the UI consider disabling the relevant UI controls or displaying a modal dialog.

    • Marked as answer by fimu1 Friday, July 3, 2015 12:57 PM
    Friday, July 3, 2015 12:12 PM
  • I suggest you refactor that code and use async await.

    Put this lot into a Task. I mean a named task

                        object[] obj = o as object[];
                        MySQL_DB db = (obj[0] as MySQL_DB);
                        Model m = (obj[1] as Model);
                        m.Model_ID = db.Get_models_ModelID(m.Model_name);
                        m.Model_number = db.Get_models_ModelNo(Convert.ToUInt16(m.Model_ID));
                        m.Model_MI_ID = db.Get_chipmount_ModelID(Convert.ToUInt16(m.Model_ID));
                        m.Model_pattern_count = Convert.ToByte(db.Get_chipmount_ModelPatternCount(m.Model_MI_ID));
                        object[] data = new object[] { m, (db.Get_ecs_last_module_SN(Convert.ToUInt16(m.Model_ID)) + 1), db.Get_GeneratedButNotUsedModulesCount((UInt16)m.Model_MI_ID) };
    
     

    In an async method you can then do something like

    MyStuff = await GetTheStuff()
    SetTheUI(MyStuff);

    Due to the magic that is async await that will fire up your new refactored GetTheStuff Task.

    That goes off on another thread, does it's thing and whilst it's doing that the rest of the async method containing it is just sort of paused.

    Once it's done it returns the result to MyStuff.

    Then SetTheUI is called.

    There is something else I would like to mention.

    Entity framework.

    If you can use it then EF can make this sort of thing way easier because there are some async methods available. 

    For example.

            protected async override void GetData()
            {
                ThrobberVisible = Visibility.Visible;
                ObservableCollection<CustomerVM> _customers = new ObservableCollection<CustomerVM>();
                var customers = await (from c in db.Customers
                                        orderby c.CustomerName
                                        select c).ToListAsync();
                foreach (Customer cust in customers)
                {
                    _customers.Add(new CustomerVM { IsNew = false, TheEntity = cust });
                }
                Customers = _customers;
                RaisePropertyChanged("Customers");
                ThrobberVisible = Visibility.Collapsed;
            }

    The above code uses ToListAsync so that the linq query is awaitable.

    So that's doing the async thing with no explicit extra task.

    When you first read that code you might think it's just going and getting the data then manipulating it and it does each thing one after the other.
    When it hits that await processing of the next line only happens when the result is returned.


    Hope that helps.

    Technet articles: WPF: Change Tracking; All my Technet Articles

    Friday, July 3, 2015 12:40 PM
  • I need that wait because I need 'Model', 'PatternCount' and 'FirsSN' properties to be filled (assigned) before I continue my code...

    Friday, July 3, 2015 12:47 PM
  • Updating the UI from any place other then the original UI thread can cause issues, wich is why it is forbidden since .NET 2.0

    The proper way to do UI updates is to use invoke to put the change orders into the Event Queue of said UI Thread.

    http://stackoverflow.com/questions/14703698/invokedelegate/14703806#14703806

    I disagree.

    That's the way to do it if you have no choice.

    If you can conveniently return the data you got to the UI thread you don't need any dispatcher / control invoke to run code on the UI thread.

    Also that ought to be begininvoke for windows forms and invokeasync for wpf. Invoke is blocking.


    Hope that helps.

    Technet articles: WPF: Change Tracking; All my Technet Articles

    Friday, July 3, 2015 12:49 PM
  • I disagree as well as Andy Oneil stated...
    Friday, July 3, 2015 12:56 PM
  • Ok I realized that deadlock, but how will I know that the ContinueWith block has run?

    Without properly set properties my program fail...

    Friday, July 3, 2015 12:59 PM
  • Have you considered using async/await as Andy ONeill suggested? Here's a simplified example:

    private async void button1_Click(object sender, EventArgs e)
    {
        button1.Enabled = false;
        textBox1.Enabled = false;
    
        string result = await Task.Run(() => {
            Thread.Sleep(1000);
            return "foobar";
        });
    
        textBox1.Text = result;
    
        textBox1.Enabled = true;
        button1.Enabled = true;
    }
    

    Disabling/enabling controls is not required, it's there to show that you may consider disabling certain parts of the UI to prevent the user from performing actions while a background operation is in progress. Your use of Wait would have achieved that by blocking the UI thread.

    If you're using an older version of Visual Studio that doesn't support async/await (VS2010 perhaps) then you could simply put the code that needs PatternCount & co. in the ContinueWith block. That is:

    Task.Factory.StartNew(... {
        ...
    }).ContinueWith(... {
        ...
        DoSomethingWithPatternCount();
    });

    Instead of:

    Task.Factory.StartNew(... {
        ...
    }).ContinueWith(... {
        ...
    }).Wait();
    
    DoSomethingWithPatternCount();
    

    Friday, July 3, 2015 1:58 PM
  • Updating the UI from any place other then the original UI thread can cause issues, wich is why it is forbidden since .NET 2.0

    The proper way to do UI updates is to use invoke to put the change orders into the Event Queue of said UI Thread.

    http://stackoverflow.com/questions/14703698/invokedelegate/14703806#14703806

    I disagree.

    That's the way to do it if you have no choice.

    If you can conveniently return the data you got to the UI thread you don't need any dispatcher / control invoke to run code on the UI thread.

    Also that ought to be begininvoke for windows forms and invokeasync for wpf. Invoke is blocking.


    Hope that helps.

    Technet articles: WPF: Change Tracking; All my Technet Articles

    Moot point. He already ran into a cross-thread exception. So he already TRIED to update the UI from an altenrante thread. Wich is why he got a Cross Thread exception in the first place.
    Invoke is the simple fix to keep most of the code without breaking the sequence.

    Complete redesign so the UI thread itsellfs busy-waits for that data works too. But has all the drawbacks of busy-waiting approaches and a total redesign.

    Friday, July 3, 2015 6:44 PM