繁体   English   中英

我可以在后台运行多个慢进程,因此可以并行运行多个任务吗?

[英]Can I run multiple slow processes in the background so more than one task can run in parallel?

我在Core .NET 2.2框架的顶部使用C#编写了一个控制台应用程序。

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

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

  1. 使用Mime消息调用远程API,其中每条消息代表一个文件。
  2. 解析Mime消息并将每条消息转换为MemoryStream创建MemoryStream的集合

一旦我有一个包含多个1000+ MemoryStream的集合,我想将每个Stream写入Azure Blob存储。 由于对远程存储的写入速度很慢,我希望我可以使用自己的进程或线程执行每次写入迭代。 这将允许我同时并行运行1000+线程,而不必等待每次写入操作的结果。 每个线程都将负责记录在写入/上载过程中可能发生的任何错误。 任何记录的错误都将使用不同的作业处理,因此我不必担心重试。

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

在测试时,我发现我对调用async理解有些无效。 我的印象是,当调用使用async定义的方法时,将在后台线程/ worker中执行,直到该过程完成。 但是,当我测试代码时,我的理解失败了。 我的代码告诉我,如果不添加关键字awaitasync代码永远不会真正执行。 同时,当添加关键字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
    }
}

从上面的代码中,调用await Upload(file); 工作,并将按预期上传文件。 但是,由于我在调用Upload()方法时使用await ,因此在上传代码完成之前,我的循环不会跳转到下一次迭代。 同时,删除await关键字,循环不等待上传过程,但Stream实际上从不写入存储,就像我从未调用过代码一样。

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

将列表转换为“上传”任务列表,并使用Task.WhenAll()等待它们:

public async Task Run()
{
    // This gets populated after calling the web-API and parsing out the result
    List<Stream> files = new List<MemoryStream>{.....};
    var tasks = files.Select(Upload);

    await Task.WhenAll(tasks);
}

有关tasks / await的更多信息,请参阅此文章

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

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

你想要做的就是:

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

在这里,我们只是尽可能快地创建任务,然后等待它们全部完成。 我们只是让.Net框架来处理剩下的事情。

这里重要的是Threads不会提高等待网络资源的速度。 任务是一种从线程手中委派需要完成的工作的方法,因此您可以有更多线程来执行任何操作(例如,启动新上载或响应完成的上载)。 如果线程只是等待上传完成,那就是浪费资源。

你可能需要这个:

var tasks = files.Select(Upload);
await Task.WhenAll(tasks);

请注意,它会产生与文件一样多的任务,如果有太多这些任务,可能会导致进程/机器关闭。 请参阅让一组任务一次只运行X作为n示例如何解决该问题。

其他答案很好,但另一种方法是你的TPL DataFlow在Nuget中可用,来自https://www.nuget.org/packages/System.Threading.Tasks.Dataflow/

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
}

数据流的优势

  1. WhenAll基于任务的解决方案一样,它是否自然地与async一起使用
  2. 它还可以用于更大的任务管道
    • 您可以通过将它们重新输入来重试错误。
    • 将任何预处理调用添加到较早的块中
  3. 如果需要关注限制,您可以限制MaxDegreeOfParallelism
  4. 您可以创建更复杂的管道,因此可以使用DataFlow的名称

您可以将代码转换为Azure功能 ,并让Azure处理大部分并行操作,横向扩展并上载到Azure Blob存储工作。

您可以使用Http Trigger或Service Bus触发器来启动每个下载,处理和上载任务。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM