0

I have a few objectives, which lead to this combination of code:

  1. Call some API functions async, so they run parallel and don't freeze the UI
  2. Show a progressBar in the meantime as the comboBoxes get filled with the API-data

I would like to keep my tasks in a List or other data structure in order to count them and assign this count value to the Maximum property in my progressBar.

public async void retrieveAPIData()
{
    // hide everything except the progressbar
    var controls = this.Controls.Cast<Control>().Where(
        control => !control.Equals(progressBar));
    foreach (var control in controls)
    {
        control.Visible = false;
    }

    // handler for when the progress gets Report-ed (Report function)
    var progress = new Progress<int>(percent =>
    {
        progressBar.Value += percent;
    });

    // api singleton
    var proErp = ProERP.Instance;

    // attempt at a List that combines tasks to their comboBox through a data class
    List<ControlFiller> tasks = new List<ControlFiller>()
    {
        new ControlFiller()
        {
            task = proErp.GetColumns(progress),
            ComboBox = squareHollowSection
        },
        new ControlFiller()
        {
            task = proErp.GetProfiles(progress),
            ComboBox = typeOfProfile
        },
    };

    // most important reason to store the tasks in a List, to count them to know how
    // many steps there will be
    progressBar.Maximum = tasks.Count;

    //var columnsTask = proErp.GetColumns(progress);
    //var profilesTask = proErp.GetProfiles(progress);

    //this.squareHollowSection.DataSource = await columnsTask;
    //this.typeOfProfile.DataSource = await profilesTask;

    // The design time errors occur here!
        foreach (var task in tasks)
        {
            task.ComboBox.DataSource = await task.task;
        }

    // To not hide the progressBar too fast for development
    Thread.Sleep(2000);
    // tasks finished
    progressBar.Visible = false;
    // show the other Controls again
    foreach (var control in controls)
    {
        control.Visible = true;
    }
}

In the task I call (example from Profile):

public async Task<List<Profile>> GetProfiles(IProgress<int> progress)
{
    var request = new RestRequest("Profile", Method.GET);
    IRestResponse response = await client.ExecuteAsync(request);
    progress.Report(1);
    return new JsonDeserializer().Deserialize<List<Profile>>(response);
}

ControlFiller class:

internal class ControlFiller
{
    public Task task;
    public ComboBox ComboBox;
}

However this does not work as I use Task in ControlFiller which returns void, I however can neither return as specific class as it would only work for 1 task or Object as the Task Generic of Task<Object> cannot be converted implicitly. Is there a way to create a list of tasks to count, let them run and assign their awaited value to a DataSource for my form?

The linked answer is not even helpful as I want to assign the returned values dynamically, now I need to know which value sits in which index.

edit:

enter image description here Even with a common foreach loop I cannot await the values as my DataStructure will cast(?) Task<Column> and Task<Profile> to a Task that doesn't return anything (void). Is there a way to

  1. Count the Tasks (not necessarily in a Data Structure).
  2. If they are added to a Data Structure, await their values and assign it to their respective ComboBox.

Self answer (which I cannot post because the question is closed) I moved the assigning of the ComboBox values to the Task itsels, than I don't need the return value.

  public async void retrieveAPIData()
        {
            var controls = this.Controls.Cast<Control>().Where(control => !control.Equals(progressBar));

            foreach (var control in controls)
            {
                control.Visible = false;
            }

            var progress = new Progress<int>(percent =>
            {
                progressBar.Value += percent;
            });
            var proErp = ProERP.Instance;

            var GetColumnsTask = proErp.GetColumns(progress, squareHollowSection);
            var GetProfileTask = proErp.GetProfiles(progress, typeOfProfile);

            ArrayList list = new ArrayList()
            {
                GetColumnsTask, GetProfileTask
            };

            await GetColumnsTask;
            await GetProfileTask;

            progressBar.Maximum = list.Count;

            Thread.Sleep(2000);
            progressBar.Visible = false;
            foreach (var control in controls)
            {
                control.Visible = true;
            }
        }

Task


 public async Task<List<Profile>> GetProfiles(IProgress<int> progress, ComboBox comboBox = null)
        {
            var request = new RestRequest("Profile", Method.GET);
            IRestResponse response = await client.ExecuteAsync(request);
            progress.Report(1);
            var data = new JsonDeserializer().Deserialize<List<Profile>>(response);
            comboBox.DataSource = data;
            return data;
        }

online Thomas
  • 8,864
  • 6
  • 44
  • 85
  • What exactly do `proErp.GetColumns` and `proErp.GetProfiles` return? – Fildor Jan 19 '21 at 16:17
  • 1
    My first observation is the `async void retrieveAPIData` method. You may want to take a look at this: [Avoid async void](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#avoid-async-void). – Theodor Zoulias Jan 19 '21 at 16:17
  • @Fidor `Task>` and `Task>` respectively – online Thomas Jan 19 '21 at 17:06
  • @theodor-zoulias the problem lies not with the encapsulating function but the calls made inside it, which are pointed out with comments. They do return results. – online Thomas Jan 19 '21 at 17:10
  • There is another hidden `async void` here: `tasks.ForEach(async...`. Unfortunately the C# language makes it very easy to create `async void` lambdas by mistake, so we must be careful every time we use the `async` keyword with a lambda. Many APIs do not understand async delegates, and the `List.ForEach` is one of them. – Theodor Zoulias Jan 19 '21 at 17:38
  • Can't you just replace the problematic `tasks.ForEach(async` with a standard `foreach` loop? If you can't, then my suggestion would be to do it anyway (by editing the question), so that we have some unique problem to solve. Currently you are just using an API incorrectly, and the [linked question](https://stackoverflow.com/questions/18667633/how-can-i-use-async-with-foreach) deals exactly with the same incorrect usage of the specific API. – Theodor Zoulias Jan 20 '21 at 07:29
  • @TheodorZoulias edited the question – online Thomas Jan 20 '21 at 07:39
  • @TheodorZoulias I changed to main code to also use a common foreach – online Thomas Jan 20 '21 at 07:49
  • Btw does it solve the problem if you change the type of the `ControlFiller.task` property from `Task` to `Task>`? – Theodor Zoulias Jan 20 '21 at 08:00
  • @TheodorZoulias The problem is that the other task returns `Task`, so I need a more general solution. I even tried to have them both implement a shared interface and declare the Task as `Task`, but even that doesn't work. – online Thomas Jan 20 '21 at 08:03
  • 1
    I see. Is any of these questions helpful? [Cannot convert type 'Task' to 'Task'](https://stackoverflow.com/questions/37818642/cannot-convert-type-taskderived-to-taskinterface) and [Why is Task not co-variant?](https://stackoverflow.com/questions/30996986/why-is-taskt-not-co-variant) – Theodor Zoulias Jan 20 '21 at 08:09
  • 1
    @TheodorZoulias Well if I declare my List as `List>` then I can't even add the tasks. But maybe if I cast them before I add them. – online Thomas Jan 20 '21 at 08:19
  • Not a duplicate of the linked question, but is of: "[progress tracking of multiple async calls](https://stackoverflow.com/q/8902756/90527)", "[How to do progress reporting using Async/Await](https://stackoverflow.com/q/19980112/90527)" – outis Nov 10 '21 at 09:19

0 Answers0