-1

I am creating an instance of a class that is in DLL and can't be modified. It loads images and it takes long time which freezes the WinForms UI.

Can I instantiate the class on a new thread?

var images = new AppImages(); // This to execute on new thread?
var cboData = new List<string>();
foreach(var image in images)
{
    cboData.Add(image); 
}
comboBox.DataSource = cboData;

I am trying to use

private void My()
{
    var images = ThreadPool.QueueUserWorkItem(GetAppImages);
     
    var cboData = new List<string>();
    foreach(var image in images)
    {
        cboData.Add(image); 
    }
    comboBox.DataSource = cboData;
}

private AppImages GetAppImages()
{
    return new AppImages();
}

but the threadPool doesn't return any value, it is just executing the code and I need the new instance to work with it later in the code.

Also, I can call the entire logic in a new thread because there are UI elements (the comboBox for example).

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
st_stefanov
  • 1,147
  • 1
  • 10
  • 28
  • 1
    We'd really need to know more about the class. Does it contain any UI components, or is it just data? Does it have any thread-sensitive behavior? – Jon Skeet Jul 21 '22 at 06:44
  • Have you tried it? Does AppImages create ui elements (control etc) – Firo Jul 21 '22 at 06:44
  • The class is just data, the issue is to return the new instance from the ThreadPool. – st_stefanov Jul 21 '22 at 06:46
  • Does this answer your question? [Accessing a form's control from a separate thread](https://stackoverflow.com/questions/7609839/accessing-a-forms-control-from-a-separate-thread) – Martheen Jul 21 '22 at 06:57
  • No, it is not related and not applicable to my case. Thera are too many UI elements involved. – st_stefanov Jul 21 '22 at 06:59
  • Another thread does not mean your UI will not block, since you want to *wait* for it to use it's result. You will need to restructure your whole method and have some kind of BackgroundWorker and Waiting Dialog/UI. – nvoigt Jul 21 '22 at 07:05
  • 1
    @nvoigt: The UI can still be responsive even if some operation is awaiting a result. – Jon Skeet Jul 21 '22 at 07:06
  • 1
    @JonSkeet Sure, but it is extra work. At least the function needs to be async, which means there needs to be some additional logic that the user cannot just click other things while it runs. – nvoigt Jul 21 '22 at 07:20
  • @nvoigt: Why does there need to be additional logic? We don't know whether the application actually needs to prevent other things from being used while the combobox is being populated or not. In my experience a lot of the time it's fine to just let the rest of the UI be usable - but it entirely depends on the application. – Jon Skeet Jul 21 '22 at 07:22
  • @JonSkeet I would assume that the App should at least block the user from clicking on the button multiple times, if it's a long running, costly operation. I haven't done WinForms in a long time, last time I checked UI callbacks were still expecting sync handlers, so putting in an async handler might cause problems in other functions the user could now reach, since the program no longer blocks. That is not a problem with async per se and your solution is absolutely correct, I'm just saying: there probably is extra work to be done now syncronising the rest for any non trivial program. – nvoigt Jul 21 '22 at 07:25
  • @nvoigt: What button? The OP never mentioned a button. Even if it *is* in response to a button, async/await makes that really easy - just disable the button at the start of the method, and re-enable it in a finally block. It's absolutely fine to write async event handlers, and *much, much* simpler than trying to manage everything via callbacks. – Jon Skeet Jul 21 '22 at 07:49
  • @JonSkeet I don't disagree. I just said they need to be aware of it, that the method is now non-blocking when the return value is ignored, it was blocking before under those circumstances and since that is the normal way WinForms calls it's event handlers, they need to adjust their application if it implicitly build on that blocking behaviour. – nvoigt Jul 21 '22 at 08:01
  • @nvoigt: Well, you also said "You will need to restructure your whole method and have some kind of BackgroundWorker and Waiting Dialog/UI" - and I still think that's inaccurate. Yes, the application needs to be designed with this sort of asynchrony in mind, but there's no need for BackgroundWorker and I don't think we should *assume* there's any need for a waiting dialog. – Jon Skeet Jul 21 '22 at 08:13
  • @JonSkeet Okay, fair enough. In my mind, an async method is "some kind" of Background worker. I didn't mean to imply they need to use that exact class and it's mechanisms. There is indeed no technical neccessity to change anything, it's just what is needed in my experience in the programs I wrote. Because my users complain when nothing happens and no progress dialog shows and my users click everywhere in the meantime instead of just being patient. Others may have better users and I am a little envious of that :) – nvoigt Jul 21 '22 at 08:17

3 Answers3

8

I would suggest using Task.Run to initialize AppImages in a different thread, and await that task from the UI thread. So:

public async Task My()
{
    Task<AppImages> task = Task.Run(() => new AppImages());
    var images = await task;
    comboBox.DataSource = images.Images.ToList();
}

The use of await here means that the last line of the method still runs on the UI thread - but it won't block the UI while the task is running.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • It's compact! it's working! The images are loaded, the combo box is populated like before and the UI doesn't freeze while loading! Accepting the answer. – st_stefanov Jul 21 '22 at 07:41
1

You need to use Invoke. This can be used by non-UI threads to access UI elements (via the UI thread). This is done because only the UI thread can interact with UI elements.

Here's an example of the creation of a new thread, and waiting for 10 seconds just to pretend it is doing work, but after waiting (e.g. when the results/images are ready), and we want to modify UI elements we use Invoke.

private void button1_Click(object sender, EventArgs e) {
    Task.Run(() => {
        // load stuff that takes time: simulate by sleeping for 10 seconds
        Thread.Sleep(10000);
        
        var newText = "new button text";

        // now we want to change something in the UI, we use Invoke.
        this.Invoke(new Action(() => {
            this.button1.Text = newText;
        }));
    });
}
Lzh
  • 3,585
  • 1
  • 22
  • 36
  • 1
    I would expect it to be simpler to use async/await - in a UI method, create a task that initializes the data in a new thread, and await that, then use the value. – Jon Skeet Jul 21 '22 at 07:06
  • I tried using async/await but the class/method that I am calling is in DLL and is not Async. When I call await it won't work. – st_stefanov Jul 21 '22 at 07:07
  • 1
    @st_stefanov: That doesn't mean you can't use async/await - it means you need to create a task yourself. See my answer. (But if you don't understand async/await, I strongly suggest you read more about it first...) – Jon Skeet Jul 21 '22 at 07:09
  • Well, I do understand what you mean in the last explanation. Thank you! – st_stefanov Jul 21 '22 at 07:18
  • @JonSkeet I agree async/await is better/cleaner/simpler than using `.Invoke` +1, thanks for your comment – Lzh Jul 21 '22 at 08:54
0

You can wrap this code in a method and can call that method using

System.Threading.Tasks.Task.Run()

eg

void LoadData()
{ 

   var images = new AppImages(); // This to execute on new thread?
   var cboData = new List<string>();
   foreach(var image in images)
   {
      cboData.Add(image); 
   }
   comboBox.DataSource = cboData;
}

and call it like this

Task.Run(() => LoadData());

Please refer the doc from Microsoft Task.Run Method

Yogesh Naik
  • 102
  • 8