在新任务 c# 中调用异步方法

Call async method in new task c#

我正在尝试解决的问题:

对于每个目录,它存在一些文件,我想把它上传到Azure。

所以我想这样做: 任务 1 - 将目录 1 中的文件上传到 azure 任务 2 - 将目录 2 中的文件上传到 azure

我想同时做这个。

我有以下代码:

private async Task ProcessMatFiles(string directory, List<FileInfo> matFiles)
{
    foreach (var file in matFiles)
    {
        if (!string.IsNullOrEmpty(file.Name) && !string.IsNullOrEmpty(directory) && !string.IsNullOrEmpty(file.FullName))
        {
            var cloudBlockBlob = this._cloudBlobContainer.GetBlockBlobReference("textures/" + directory + "/" + file.Name);

            if (!await cloudBlockBlob.ExistsAsync())
                await cloudBlockBlob.UploadFromFileAsync(file.FullName);
        }
    }
List<Task> tasks = new List<Task>();
foreach (var directory in matFileDirectories)
{
    // Get all the files in the directory
    var matFiles = new DirectoryInfo(directory).EnumerateFiles().ToList();

    // Get the directory name of the files
    var matDirectory = Path.GetFileName(Path.GetDirectoryName(matFiles.FirstOrDefault().FullName));

    if (matFiles.Count > 0 && !string.IsNullOrEmpty(matDirectory))
    {
        var task = new Task(() =>this.ProcessMatFiles(matDirectory, matFiles));
        tasks.Add(task);
        task.Start();
    }
}

Task.WaitAll(tasks.ToArray());

使用此代码,我收到以下警告:

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

这是什么意思?这对我的代码有何影响?

我可以这样删除警告:

var task = new Task(async () => await this.ProcessMatFiles());

这是正确的方法吗?

真正的问题似乎是如何并行处理多个文件。 ProcessMatFiles returns 已经 Task 我认为它不会 运行 调用者线程上的任何内容。该任务可以存储在 tasks 列表中。可以等待该列表而不会阻塞

await Task.WhenAll(tasks);

更好的解决方案是将整个循环转换为 returns 任务的 LINQ 查询并等待它。

var tasks = from var directory in matFileDirectories
            let dir=new DirectoryInfo(directory)
            let files=dir.GetFiles()
            select ProcessMatFiles(dir.Name, files));

await Task.WhenAll(tasks);

这样做的问题是枚举文件夹中的文件本身就很昂贵,并且GetFiles()或使用EnumerateFiles().ToList()必须等待枚举完成。如果 ProcessMatFiles 收到 DirectoryInfo 对象并在单独的线程 中枚举文件 .

会更好

另一个改进是逐个处理文件:

var tasks = from var directory in matFileDirectories
            let dir=new DirectoryInfo(directory)
            from file in dir.EnumerateFiles()
            select ProcessMatFile(dir.Name, file));

如果知道 ProcessMatFiles 的作用,则可以进一步改进这一点,例如使用数据流块或通道进行节流并使用特定数量的任务,将流程分解为多个并发步骤等。

更新

由于这是一个文件上传操作,每个文件都是一个单独的异步操作。使用 DirectoryInfo 和 FileInfo 对象时,可以删除大部分检查。

上传方法应该是:

async Task Upload(FileInfo file)
{
    var folder=file.Directory.Name;
    var blob = _cloudBlobContainer.GetBlockBlobReference(${"textures/{folder}/{file.Name}";
    if (!await blob.ExistsAsync())
    {
        await blob.UploadFromFileAsync(file.FullName);
    }
}

任务生成查询可以简化为:

var tasks = from var directory in matFileDirectories
            let dir=new DirectoryInfo(directory)
            from file in dir.EnumerateFiles()
            select UploadFile(file);

await Task.WhenAll(tasks);

这将尝试以与文件迭代一样快的速度触发所有上传操作。这可能会淹没网络。一种解决方案是使用 ActionBlock 一次仅使用 8 个任务来上传文件。输入缓冲区也有限制,以避免用例如 1000 个 FileInfo 项填充它:

var options=new ExecutionDataflowBlockOptions
      {
         MaxDegreeOfParallelism = 8,  //Only 8 concurrent operations
         BoundedCapacity=64           //Block posters if the input buffer has too many items
      } ;
var block=new ActionBlock<FileInfo>(async file=>UploadFile(file),options);

var files = from var directory in matFileDirectories
            let dir=new DirectoryInfo(directory)
            from file in dir.EnumerateFiles()
            select file;

foreach(var file in files)
{
    //Wait here if the input buffer is full
    await block.SendAsync(file);
}

block.Complete();

//Wait for all uploads to finish
await block.Completion;