0

I was trying to add some controls using a loop in to flowlayoutpanel from other thread than the main thread but I can't do it. Any Help ? This code is running on UI thread

    private bool ShowProducts(string Page ,string KeyWord)
    {
        string Res = AO.GetPageInfo(Page ,textBox1.Text);
        if(Res == ""){
            return false;
        }

        label12.Text = Res;
        CurrentPage = int.Parse(Page);
        textBox3.Text = Page;
        //flowLayoutPanel2.Visible = false;
        flowLayoutPanel2.Controls.Clear();

        Products = AO.SearchProducts(CurrentPage.ToString(), textBox1.Text);

        foreach(Product X in Products)
              flowLayoutPanel2.Controls.Add( new Card(X) );

        return true;
    }
  • can you share the code here? – praty Aug 10 '17 at 09:31
  • added the Code. – Yupatshi Makni Aug 10 '17 at 09:36
  • Also next time please post the `exception`/`error` you get! – Peter Aug 10 '17 at 09:38
  • where ever you are calling this method, can you try calling it as `formObject.Invoke(() => ShowProducts("arg1", "arg2")` – praty Aug 10 '17 at 09:42
  • If this code is running on the **UI thread** and you have the `FlowLayoutPanel` in the same place, it should work. Post the exception – Jakub Dąbek Aug 10 '17 at 09:46
  • It's working fine but it takes time and the UI thread is not responding. I want it to be responfing – Yupatshi Makni Aug 10 '17 at 09:52
  • You can use `async/await` - https://learn.microsoft.com/en-us/dotnet/csharp/async – Jakub Dąbek Aug 10 '17 at 09:54
  • What are the longest operations in the function? You'd have to wrap them into `Task.Run` or run them asynchronously – Jakub Dąbek Aug 10 '17 at 10:05
  • adding the card user control to the flowlayout – Yupatshi Makni Aug 10 '17 at 10:08
  • That would be weird unless the `Card` constructor is really slow or the number of products is really large. I suspect `AO.SearchProducts` or `AO.GetPageInfo`. What do these do? – Jakub Dąbek Aug 10 '17 at 10:13
  • no they are just getting products from website it takes less 2 seconds to complete it but the Card user control contains 1 picture box and 4 labels and adding at max 50 of them in the flow layout. – Yupatshi Makni Aug 10 '17 at 10:16
  • So the culprit is getting the products. Adding controls probably takes fraction of a second, but network operations are really slow. You don't have `async` versions of these? Are these your custom functions? – Jakub Dąbek Aug 10 '17 at 10:18
  • no AO.SearchProducts and AO.GetPageInfo takes less than 2 seconds to complete the function but adding cards takes about 10 seconds or more. Yes these are functions from a class I created. but I don't really know what the async is. – Yupatshi Makni Aug 10 '17 at 10:22

2 Answers2

0

If you want to do something in the UI Thread from another Thread, you have to Invoke what you want to do.

Then you can easy "take control" like this:

this.Invoke((MethodInvoker)delegate {
       Button button = new Button();
       myFlowLayoutPanel.Controls.Add(button);
     });

Hope this helps you.

  • Okay I tried this but nothing was added foreach (Product P in Products) flowLayoutPanel2.Invoke((MethodInvoker)delegate { Controls.Add(new Card(P)); }); – Yupatshi Makni Aug 10 '17 at 09:40
  • Do not Invoke the FlowLayoutPanel. Invoke the Form Class you are in. Like : this.Invoke (if you are in a form class) or myForm.Invoke. –  Aug 10 '17 at 09:42
  • You have to do `myFlowLayoutPanel.Controls.Add(something);`. I think we're all confusing whats going on – Jakub Dąbek Aug 10 '17 at 09:43
  • yeah ;D -> If you are let's say in your Form.cs do following in your Thread: foreach (Product P in Products) this.Invoke((MethodInvoker)delegate { MyFlowLayoutPanel.Controls.Add(new Card(P)); }); –  Aug 10 '17 at 09:45
  • What I mean I want to add controls to the flowlayoutpanel without making the UI thread unresponding is there any way to do this ? – Yupatshi Makni Aug 10 '17 at 09:48
  • So that means that everything works now but it blocks the UI? – Jakub Dąbek Aug 10 '17 at 09:52
  • Of course. If you taking control of the UI-Thread-Element, it has to be processed. Maybe you can put the foreach returned things in a List and then add this List in one "punch" to the flowlayoutPanel with the Invoke –  Aug 10 '17 at 09:58
  • https://pastebin.com/ZG1Dq6XT Thats the way i would do it. I dont know what you are doing in your Card class but this way it should not block the UI. YES it will always block the flowlayoutpanel for a short time because WinForms has to render the new control! –  Aug 10 '17 at 10:49
0

The thing is that with such long-running operations, you want to use async/await. The code would look more or less like this:

private async Task<bool> ShowProducts(string page, string keyWord)
{
    string text = textBox1.Text;
    string res = await Task.Run(() => AO.GetPageInfo(page, text)); //run on a thread pool
    if(res == "")
    {
        return false;
    }

    label12.Text = res;
    CurrentPage = int.Parse(page);
    textBox3.Text = page;
    //flowLayoutPanel2.Visible = false;
    flowLayoutPanel2.Controls.Clear();

    Products = await Task.Run(() => AO.SearchProducts(CurrentPage.ToString(), text)); //run on a thread pool

    //foreach(Product X in Products)
    //    flowLayoutPanel2.Controls.Add(new Card(X));

    //This code will block only for the time of adding the controls.
    //This is unavoidable as flowLayoutPanel2 is part of the UI 
    //and has to be updated on the UI thread.
    //Making the controls is done on a thread pool so it won't block
    flowLayoutPanel2.Controls.AddRange(await Task.Run(() => Products.Select(x => new Card(x)).ToArray()));

    return true;
}

This would let the UI thread work while the function executes. But you would have to run it like

bool result = await ShowProducts("1", "keyWord");

so you'd have to call it from an async method. I advise you to read about them and implement them in your code.


Some resources:

Stephen Cleary's intro - it's quite old but still relevant

Microsoft docs

A Stack Overflow question about async/await

Jakub Dąbek
  • 1,044
  • 1
  • 8
  • 17
  • Thank you very much you solved my problem. Thanks for your time. You are the best :) – Yupatshi Makni Aug 10 '17 at 11:11
  • Hey jakub thank you for help. but what if I have multiple functions like this function an I want them to run in the same time ? Can you help me ? – Yupatshi Makni Aug 10 '17 at 18:17
  • I don't know exactly what you mean. If you can explain it really quickly here, then I may help, but otherwise create a new question. – Jakub Dąbek Aug 10 '17 at 18:52
  • I used the function you wrote and it worked now I have two functions running with the same idea used in this code but I want both functions to run simultaneously. And I can't find a way to do it. – Yupatshi Makni Aug 10 '17 at 18:57
  • Try something from [here](https://stackoverflow.com/questions/12932102/starting-multiple-async-await-functions-at-once-and-handling-them-separately) or [here](https://stackoverflow.com/questions/25933159/await-multiple-tasks-to-be-shown-in-gui) – Jakub Dąbek Aug 10 '17 at 19:06