C# 我是否可以在后台运行多个慢速进程,以便多个任务可以并行运行?

C# 我是否可以在后台运行多个慢速进程,以便多个任务可以并行运行?,c#,multithreading,asynchronous,async-await,task-parallel-library,C#,Multithreading,Asynchronous,Async Await,Task Parallel Library,我在Core.NET2.2框架的顶部编写了一个控制台应用程序,使用的是C 我的应用程序允许我使用Windows任务调度器触发长时间运行的管理作业 其中一个管理作业进行web API调用,在将大量文件上载到Azure Blob存储之前下载这些文件。以下是我的代码完成任务所需执行的逻辑步骤 调用远程API,该API使用Mime消息进行响应,其中每条消息表示一个文件 解析Mime消息并将每条消息转换为MemoryStream创建一个MemoryStream集合 一旦我有了一个包含多个1000+Memo

我在Core.NET2.2框架的顶部编写了一个控制台应用程序,使用的是
C

我的应用程序允许我使用Windows任务调度器触发长时间运行的管理作业

其中一个管理作业进行web API调用,在将大量文件上载到Azure Blob存储之前下载这些文件。以下是我的代码完成任务所需执行的逻辑步骤

  • 调用远程API,该API使用Mime消息进行响应,其中每条消息表示一个文件
  • 解析Mime消息并将每条消息转换为
    MemoryStream
    创建一个MemoryStream集合
  • 一旦我有了一个包含多个1000+
    MemoryStream
    的集合,我想将每个
    写入Azure Blob存储。由于对远程存储的写入速度很慢,我希望可以使用自己的进程或线程执行每个写入迭代。这将允许我同时并行运行1000多个线程,而不必等待每个写操作的结果。每个线程将负责记录写入/上载过程中可能发生的任何错误。任何记录的错误都将使用不同的作业处理,因此我不必担心重试

    我的理解是,调用异步写入/上传流的代码正好可以做到这一点。换句话说,我会说“有一个
    执行它并运行它所需要的时间。只要任务完成,我并不真正关心结果。”

    在测试过程中,我发现我对调用
    async
    的理解有些无效。我的印象是,当调用用
    async
    定义的方法时,将在后台线程/工作线程中执行,直到该过程完成。但是,当我测试代码时,我的理解失败了。我的代码告诉我,如果不添加关键字
    await
    async
    代码永远不会真正执行。同时,当添加关键字
    await
    时,代码将等待进程完成执行后再继续。换句话说,根据我的需要添加
    await
    ,将无法异步调用该方法

    这里是我的代码的精简版本,以解释我试图实现的目标

    public async Task Run()
    {
        // This gets populated after calling the web-API and parsing out the result
        List<Stream> files = new List<MemoryStream>{.....};
    
        foreach (Stream file in files)
        {
            // This code should get executed in the background without having to await the result
            await Upload(file);
        }
    }
    
    // This method is responsible of upload a stream to a storage and log error if any
    private async Task Upload(Stream stream)
    {
        try
        {
            await Storage.Create(file, GetUniqueName());
        } 
        catch(Exception e)
        {
            // Log any errors
        }
    }
    
    公共异步任务运行()
    {
    //在调用web API并解析出结果后,将填充此函数
    列表文件=新列表{….};
    foreach(文件中的流文件)
    {
    //此代码应该在后台执行,而不必等待结果
    等待上传(文件);
    }
    }
    //此方法负责将流上载到存储器并记录错误(如果有)
    专用异步任务上载(流)
    {
    尝试
    {
    等待存储。创建(文件,GetUniqueName());
    } 
    捕获(例外e)
    {
    //记录任何错误
    }
    }
    
    从上述代码中,调用
    等待上传(文件)正常工作,并将按预期上载文件。但是,由于调用
    Upload()
    方法时使用了
    wait
    ,因此在上传代码完成之前,我的循环不会跳转到下一个迭代。同时,删除
    await
    关键字,循环不会等待上载过程,但流从未实际写入存储,就好像我从未调用代码一样


    如何并行执行多个
    Upload
    方法,以便每次上传都有一个线程在后台运行?

    将列表转换为“Upload”任务列表,并使用以下命令等待它们:

    公共异步任务运行()
    {
    //在调用web API并解析出结果后,将填充此函数
    列表文件=新列表{….};
    var tasks=文件。选择(上载);
    等待任务。何时(任务);
    }
    
    有关任务/等待的更多信息,请参阅。

    您可能需要:

    var tasks = files.Select(Upload);
    await Task.WhenAll(tasks);
    
    请注意,它将产生与文件数量一样多的任务,如果任务太多,可能会导致进程/机器停机。请参见如何解决此问题的示例

    我希望我可以使用自己的进程或线程来执行每个写迭代

    这并不是最好的方法。进程和线程是有限的资源。您的限制因素是等待网络执行操作

    您要做的是:

    var tasks = new List<Task>(queue.Count);
    
    while (queue.Count > 0)
    {
      var myobject = Queue.Dequeue();
      var task = blockBlob.UploadFromByteArrayAsync(myobject.content, 0, myobject.content.Length);
      tasks.Add(task);
    }
    await Task.WhenAll(tasks);
    
    var tasks=新列表(queue.Count);
    而(queue.Count>0)
    {
    var myobject=Queue.Dequeue();
    var task=blockBlob.uploadfromtearrayasync(myobject.content,0,myobject.content.Length);
    任务。添加(任务);
    }
    等待任务。何时(任务);
    
    在这里,我们只是尽可能快地创建任务,然后等待它们全部完成。我们将让.Net框架来处理其余的部分


    这里重要的是线程不会提高等待网络资源的速度。任务是一种将需要完成的任务委托给线程的方式,这样您就有更多的线程来做任何事情(比如启动新的上传,或者响应完成的上传)。如果线程只是等待上传完成,那就是浪费资源。

    其他答案也可以,但是另一种方法是使用Nuget中提供的TPL数据流

    公共静态异步任务工作负载(列出结果)
    {
    var options=新的ExecutionDataflowBlockOptions
    {
    MaxDegreeOfParallelism=50
    };
    var block=新操作块(MyMethodAsync,选项);
    foreach(结果中的var结果)
    block.Post(结果);
    block.Complete();
    等待区块完成;
    }
    ...
    公共异步任务MyMethodAsync(结果)
    {       
    //在这里做异步工作
    }
    
    数据流的优势

  • 它是否自然地与
    async一起工作var tasks = new List<Task>(queue.Count);
    
    while (queue.Count > 0)
    {
      var myobject = Queue.Dequeue();
      var task = blockBlob.UploadFromByteArrayAsync(myobject.content, 0, myobject.content.Length);
      tasks.Add(task);
    }
    await Task.WhenAll(tasks);
    
    public static async Task DoWorkLoads(List<Something> results)
    {
       var options = new ExecutionDataflowBlockOptions
                         {
                            MaxDegreeOfParallelism = 50
                         };
    
       var block = new ActionBlock<Something>(MyMethodAsync, options);
    
       foreach (var result in results)
          block.Post(result );
    
       block.Complete();
       await block.Completion;
    
    }
    
    ...
    
    public async Task MyMethodAsync(Something result)
    {       
       //  Do async work here
    }