3

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);
                   }

        }
Mr. Dawit
  • 103
  • 2
  • 10

2 Answers2

3

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}");
}
Metro Smurf
  • 37,266
  • 20
  • 108
  • 140
  • `for (int i = 0; i <= 1; i++) // only 1 loop` Will this not loop twice? (`i = 0`, `i = 1`) – Astrid E. Sep 09 '22 at 08:17
  • Yes, that's 2 loop. Doh! It's a bit unusual to use `<=`. Regardless, does the answer provide more clarity into using async/await w/ events? – Metro Smurf Sep 09 '22 at 13:07
  • Agreed, I was also surprised to read `<=`. I do find that your answer provides more clarity into _how_ to use async/await with events. A suggestion for improvement may be to include something about _why_ (e.g. _why_ `multiplePics()` should return a `Task` and `button5_Click()` should be `async`). – Astrid E. Sep 09 '22 at 14:05
  • 1
    Added a few links; rather than re-hashing the why's of when to return a Task vs void, I'll leave it up to the reader to go through the links I added. – Metro Smurf Sep 09 '22 at 14:55
  • @Metro Smurf, this didn't work, but thanks for the attempt. "but my result is maxed out at 1 iteration" means, "My attempted codes were only able to get ExecuteScriptAsync() method to download the last value of the for loop. The previous values of the for loop continue to be negated." All code attempts produce just 1 download. – Mr. Dawit Sep 15 '22 at 13:15
  • @Mr.Dawit - please post your exact code that is failing after you applied my suggestions. I'd recommend adding it as an edit to your original question, i.e., add to the question w/o deleting the original sample code. – Metro Smurf Sep 15 '22 at 16:36
  • @Metro Smurf, edited the post – Mr. Dawit Sep 15 '22 at 20:52
  • @Mr.Dawit - see my edit #2. – Metro Smurf Sep 15 '22 at 21:15
0

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);
}
  • This doesn't do what you think it does. What happens if multiple tasks complete at the same time? – David L Aug 16 '22 at 17:31
  • They are handled one by one here: "Task.WhenAny(lstImageTasks);" because, Task.WhenAny creates a task that will complete when any of the supplied tasks have completed. – Ricardo Rodrigues Aug 17 '22 at 08:25
  • This is "better" comparing with await, because, in my opinion, this starts various "multiplePics" at the same time and treats each one independent when any finishes and, for me, that is faster then just "await". – Ricardo Rodrigues Aug 17 '22 at 10:09
  • The issue is that Task.WhenAny does not _prevent_ any other tasks from completing simultaneously, and you only remove the task that is returned from Task.WhenAny. At a bare minimum, you need to check for other completed tasks. If you add a check of `Console.WriteLine(tasks.Count(x => x.IsCompleted));` after your remove line, you'll find that far more than one task is completed. In a simple dummy example with a 100 ms delay, ALL of the tasks completed. The loop then simply checks one completed task at a time at that point, which isn't efficient at all. – David L Aug 17 '22 at 13:27
  • 2
    By the time you are adding tasks to the list, they will start, so, the loop will "see" all the tasks completed in a "dummy 100ms delay". Anyway my expert friend, I use this very often with success and, as I said, your answer is the correct one (I even upvoted it). I only intend to leave another point of view/solution for the good of this awesome community. – Ricardo Rodrigues Aug 17 '22 at 14:13