使用ExecuteScriptAsync方法时for循环仅运行一次

3

环境是 C# 和 WinForms。我试图运行一个程序,它将在已创建的网站中创建一个图像下载元素。我正在使用 WebView2 作为浏览器。我将 for 循环更改为 2 次迭代,只是为了调试此问题。我可以成功下载 1 张图片,但我的结果在 1 次迭代时达到最大值。谢谢任何帮助!以下是给我带来问题的代码:

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


我已尝试:

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

我也尝试过:

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
        }

另一次尝试:

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

        }

3
你需要等待多张图片。 - Daniel A. White
1
你还应该将你的签名从 async void 改为 async Task - David L
我对C#不是很有经验,但我分别和同时遵循了两个建议。现在awaitmultiplePics之前,并且我将for循环放在了一个异步void函数中。所有这些都得到了相同的结果。 - Mr. Dawit
1
“my result is maxed out at 1” 是什么意思?当然,使用的 i 的最大值为1。您的代码只是点击了一个 <a> 元素,但没有等待下载完成。 - Klaus Gütter
2个回答

3

首先要更新multiplePics的签名以返回一个Task

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

然后,您可以在事件处理程序中包含async来使用您的方法:

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

最后,你现在可以在事件处理程序中使用你的multiplePics方法:
private async void button5_Click(object sender, EventArgs e)
{
    for (int i = 0; i <= 1; i++)
    {
        await multiplePics(i);
    }
}

然而,考虑到上述循环仅定义为迭代一次两次,需要更新循环次数,暂定为3:
private async void button5_Click(object sender, EventArgs e)
{
    for (int i = 0; i < 3; i++) // 3 loops
    {
        await multiplePics(i);
    }
}

假设以上代码现在会串行下载3个图像,您最终希望以并行方式下载它们,并且不会阻塞UI。个人建议使用BackgroundWorker,但这是完全不同的问题。
最后,如果以上仍然无法正常工作,您需要提供更多信息来解释“但我的结果在1次迭代时达到了最大值”的含义。
编辑
有关使用async/await的更多信息,请查看至少以下文章,其中详细介绍何时返回Task以及何时可以接受void(提示:仅与事件一起使用)。
C#语言设计者之一撰写的SO答案:返回void和返回Task之间有什么区别?

这是一篇由最有经验的开发人员撰写的MSDN文章,涉及async/await的最佳实践:Async/Await - Best Practices in Asynchronous Programming

同一位作者在MSDN文章中回答了一个SO问题:Why exactly is void async bad?

还有其他一些文章和SO问答可以更深入地探讨这个主题;使用相关关键字进行搜索,例如:"async void vs async task"。

编辑#2

根据我的评论,使用以下代码,该代码直接从您的最新示例中获取,但添加了结果的调试写入。

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

for (int i = 0; i <= 1; i++) // only 1 loop 这不会循环两次吗? (i = 0, i = 1) - Astrid E.
是的,这是两个循环。哎呀!使用<=有点不寻常。但无论如何,这个答案是否提供了更清晰地使用异步/等待事件的方法? - Metro Smurf
1
添加了一些链接;不再重复讲解何时返回Task而不是void的原因,我将让读者自行查看我添加的链接。 - Metro Smurf
@Metro Smurf,编辑了这篇帖子。 - Mr. Dawit
@Mr.Dawit - 请查看我的第二次编辑。 - Metro Smurf
显示剩余3条评论

0

尽管@Daniel的回答是正确的,如果确实有多个下载任务需要完成,您始终可以执行以下操作:

创建一个布尔类型的任务列表;

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

将你的void函数"multiplePics"改为bool函数,然后使用以下代码:
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);
}

这并不是你想象中的那样。如果多个任务同时完成会发生什么? - David L
这里逐个处理它们:"Task.WhenAny(lstImageTasks);",因为Task.WhenAny创建一个任务,该任务将在任何已提供的任务完成时完成。 - Ricardo Rodrigues
这个比使用await更好,因为我认为它可以同时启动多个"multiplePics"进程,并在任何一个完成时单独处理每个进程,对我来说比仅仅使用"await"更快。 - Ricardo Rodrigues
问题在于Task.WhenAny不会防止其他任务同时完成,而且您只删除从Task.WhenAny返回的任务。作为最基本的要求,您需要检查其他已完成的任务。如果您在删除行后添加“Console.WriteLine(tasks.Count(x => x.IsCompleted))”的检查,您将发现已完成的任务远远超过一个。在一个简单的虚拟示例中,延迟100毫秒,所有任务都已完成。此时,循环只逐一检查一个已完成的任务,这样效率非常低下。 - David L
2
当您将任务添加到列表时,它们将开始运行,因此循环将在“虚拟100毫秒延迟”中“看到”所有已完成的任务。无论如何,我的专业朋友,我经常使用这种方法并且非常成功,正如我所说,您的答案是正确的(我甚至点赞了它)。我只是想为这个令人敬畏的社区留下另一个观点/解决方案。 - Ricardo Rodrigues

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接