For loop only runs once with ExecuteScriptAsync method

Viewed 299

Environment is C# and WinForms. I am trying to run a program that will create an image download element in an already created website. I am using WebView2 as the browser. I changed the for-loop to 2 iterations just to debug this issue. I can download 1 image successfully but my result is maxed out at 1 iteration. Thanks for any help! Below is the code giving me issues:

   async void multiplePics (int column) => await webView2.ExecuteScriptAsync("" +
                "var downloadElement=document.createElement('a'); " +
                "downloadElement.setAttribute('download',''); " +
                "downloadElement.href= document.getElementsByClassName('slick-slide slick-cloned')[" +  column + "].getElementsByClassName('item')[0].getAttribute('href'); " +
                "document.body.appendChild(downloadElement); " +
                "downloadElement.click();" +
                "downloadElement.remove();  " +
                "");


            for (int i = 0; i <= 1; i++)
            {
                Debug.WriteLine(i);
                multiplePics( i);
            }


have tried:

async private void button5_Click(object sender, EventArgs e)
        {
         void multiplePics(int column) {
                //webView2.ExecuteScriptAsync( "javascript");
                }

         for (int i = 0; i <= 1; i++)
               {await multiplePics(i);}
        }

have also tried:

private void button5_Click(object sender, EventArgs e)
        {
         Task<string> multiplePics(int column) {
                //return webView2.ExecuteScriptAsync( "javascript");
                }

         Task.Run( ()=>{ return multiplePics(0);} );
         Task.Run( ()=>{ return multiplePics(1);} );
//tried GetAwaiter() along with GetResult() also
        }

another attempt:

private async void button5_Click(object sender, EventArgs e)
        {
     //tried public & private async Task multiplePics with no success
     //async Task multiplePics had no errors but had the same result
          private async Task multiplePics(int column) => 
                await webView2.ExecuteScriptAsync("" +
                 "var downloadElement=document.createElement('a'); " +
                 "downloadElement.setAttribute('download',''); " +
                 "downloadElement.href= document.getElementsByClassName('slick-slide slick-cloned')[" + column + "].getElementsByClassName('item')[0].getAttribute('href'); " +
                 "document.body.appendChild(downloadElement); " +
                 "downloadElement.click();" +
                 "downloadElement.remove();  " +
                 "");

                for (int i = 0; i <= 3; i++) 
                   {
                      await multiplePics(i);
                   }

        }
2 Answers

First thing is to update the signature of multiplePics to return a Task:

private async Task multiplePics (int column) => await webView2.ExecuteScriptAsync(...);

Then, you can use your method from your event handler by including async in the signature:

// event handlers use async void, not async Task
private async void button5_Click(object sender, EventArgs e)

Finally, you can now use your multiplePics method in the event handler:

private async void button5_Click(object sender, EventArgs e)
{
    for (int i = 0; i <= 1; i++)
    {
        await multiplePics(i);
    }
}

However, given the above loop is only defined to iterate once twice, update the number of loops; let's say 3 for now:

private async void button5_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 3; i++) // 3 loops
    {
        await multiplePics(i);
    }
}

Assuming the above will now download 3 images serially, you'll eventually want to download them in parallel and without blocking the UI. Personally, I would recommend using a BackgroundWorker, but that's an entirely different question.

Finally, if the above still is not working, you'll need to provide more information as to what this means: "but my result is maxed out at 1 iteration".

Edit

For more information on using async/await, I'd suggest you start with reviewing at least these posts/articles which go into some detail about when to return a Task and when void is acceptable (hint, only with events).

SO answer written by one of the C# language designers: What's the difference between returning void and returning a Task?

MSDN article written by one of the most knowledgeable devs on async/await: Async/Await - Best Practices in Asynchronous Programming

And the same author of the MSDN article answering an SO question: Why exactly is void async bad?

There are several other articles and SO q/a's that will go into even more depth on the topic; a bit of search on the relevant keywords will go a long way, ex: "async void vs async task"

Edit #2

Per my comments, use the following code that was taken directly from your latest sample, but adds a debug write for the result.

private async void button5_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 3; i++) 
    {
       await multiplePics(i);
    }
}

private async Task multiplePics(int column)
{
    var result = await webView2.ExecuteScriptAsync("" +
         "var downloadElement=document.createElement('a'); " +
         "downloadElement.setAttribute('download',''); " +
         "downloadElement.href=document.getElementsByClassName('slick-slide slick-cloned')[" + column + "].getElementsByClassName('item')[0].getAttribute('href'); " +
         "document.body.appendChild(downloadElement); " +
         "downloadElement.click();" +
         "downloadElement.remove();" +
         "");
         
    Debug.WriteLine($"Col {column} result: {result}");
}

Dispite @Daniel response is the correct one, you can always do the following if there are indeed multiple donwloads you wish to do:

Create a list of task of boolean;

List<Task<bool>> lstImageTasks = new List<Task<bool>>();

Change you void "multiplePics" to become a bool function and then use this:

for (int i = 0; i <= 1; i++)
{
    lstImageTasks.Add(multiplePics(i));
}

while (lstImageTasks.Any())
{
    Task<bol> task = await Task.WhenAny(lstImageTasks);
    var resTask = task.Result;
    if (resTask != null)
    {
        if (resTask)
        {
            //SUCESS! do whatever you want... (implement log, Console.WriteLine(), ...)
        }
        else
        {
            //UPS...! do whatever you want... (implement log, Console.WriteLine(), ...)
        }
    }
    lstFeedBackTasks.Remove(task);
}
Related