【问题标题】:How after completing any task in the loop, pass a value to another method, return to the loop, and wait for the next task to complete?如何在完成循环中的任何任务后,将值传递给另一个方法,返回循环,并等待下一个任务完成?
【发布时间】:2023-11-22 06:32:01
【问题描述】:

2 种方法一种方法依赖于另一种方法。我需要将几个文件并行发送到服务器,首先来自服务器的答案,然后将其发送到DownloadFileAsync 方法,并对其余文件执行相同操作(无需立即等待所有答案,发送它们(答案来自服务器)到DownloadFileAsync() 方法,因为它们被接收)。

public async Task CompressAllFilesAsync(List<UserFile> files, string outputpath)
{
    await UploadAllFilesAsync(files);
    await DownloadAllFilesAsync(files, outputpath);
}

public async Task UploadAllFilesAsync(List<UserFile> files)
{
    IEnumerable<Task> allTasks = files.Select(async (file, i) =>
        files[i].FileLink = await UploadFileAsync(files[i])
    );

    await Task.WhenAll(allTasks);
}

public async Task DownloadAllFilesAsync(List<UserFile> files, string outputpath)
{
    IEnumerable<Task> allTasks = files.Select((file, i) =>
        DownloadFileAsync(files[i].FileLink,
        Path.Combine(outputpath, $"{files[i].FileName}")
    ));

    await Task.WhenAll(allTasks);
}

现在程序在运行DownloadAllFilesAsync() 方法之前等待来自服务器的所有答案(可下载文件的链接)。

【问题讨论】:

    标签: c# visual-studio asynchronous task-parallel-library webrequest


    【解决方案1】:

    您可以考虑使用新的 (.NET 6) API Parallel.ForEachAsync,并为每个 UserFile 依次执行 UploadFileAsyncDownloadFileAsync,但与其他文件同时执行:

    public async Task CompressAllFilesAsync(List<UserFile> files, string outputPath)
    {
        var options = new ParallelOptions() { MaxDegreeOfParallelism = 10 };
        await Parallel.ForEachAsync(files, options, async (file, ct) =>
        {
            await UploadFileAsync(file);
            await DownloadFileAsync(file.FileLink,
                Path.Combine(outputPath, $"{file.FileName}"));
        });
    }
    

    UploadFileAsyncDownloadFileAsync 将在 ThreadPool 线程上调用。这应该不是问题,除非这些方法与线程仿射组件(如 UI 控件)交互。

    【讨论】:

    • “async (file, ct)”中的“ct”是什么?
    • @Ilya 这是一个CancellationToken,如果另一个文件失败,您可以使用它来取消对一个文件的处理(以便更快地传播错误)。看来你的UploadFileAsyncDownloadFileAsync 方法是不可取消的,所以你不能把这个令牌传递给它们,但是你可以考虑在两个调用之间截取一个ct.ThrowIfCancellationRequested()
    • 我似乎无法使用“Parallel.ForEachAsync”,在带有“目标平台 4.6.1”的 Visual Studio 2017 中,此方法不可用。
    • @Ilya 你能下载 Visual Studio 2022 并针对最新的 .NET 6 运行时吗?如果没有,那么您不能使用Parallel.ForEachAsync API。您必须找到此方法的替代方法(有许多自定义实现可用),例如我发布的here
    • 感谢您对代码的帮助,我设法找到了另一个与您的代码相同且不需要 (.NET 5 | 6) 的解决方案。
    【解决方案2】:
    public async Task CompressAllFilesAsync(List<UserFile> files, string outputpath) {
        SemaphoreSlim throttler = new SemaphoreSlim(2);
    
        IEnumerable<Task> allTasks = files.Select(async (file) => {
            await throttler.WaitAsync();
    
            try {
                file.Link = await UploadFileAsync(file);
    
                await DownloadFileAsync(file.Link,
                    Path.Combine(outputpath, file.Name));
            } finally {
                throttler.Release();
            }
        });
    
        await Task.WhenAll(allTasks);
    }
    

    【讨论】:

      【解决方案3】:
      for (int i = 0; i < fileToSend; i++)
      {    
         Task task = new Task(() => 
         {
            SendFile(file[i]);
         });
         task.Start();
      }
      

      这样你就可以创建一个后台任务来并行发送你的文件

      【讨论】:

      • 此解决方案无效,DownloadFile() 方法获取错误数据。
      • 这段代码与Task.Run()大致相同,实际上并没有等待操作完成。以Start() 明确开始的冷任务很少有任何充分的理由。任务不是线程,而是承诺。调用Start() 将与Task.Run() 做同样的事情:安排由线程池线程执行的承诺
      最近更新 更多