C# 如何限制多个异步任务?

C# 如何限制多个异步任务?,c#,asynchronous,async-await,task-parallel-library,C#,Asynchronous,Async Await,Task Parallel Library,我有以下形式的代码: static async Task DoSomething(int n) { ... } static void RunThreads(int totalThreads, int throttle) { var tasks = new List<Task>(); for (var n = 0; n < totalThreads; n++) { var task = DoSomething(n); tasks.Add(t

我有以下形式的代码:

static async Task DoSomething(int n) 
{
  ...
}

static void RunThreads(int totalThreads, int throttle) 
{
  var tasks = new List<Task>();
  for (var n = 0; n < totalThreads; n++)
  {
    var task = DoSomething(n);
    tasks.Add(task);
  }
  Task.WhenAll(tasks).Wait(); // all threads must complete
}
静态异步任务DoSomething(int n)
{
...
}
静态无效运行线程(int totalThreads、int throttle)
{
var tasks=新列表();
对于(var n=0;n
问题是,如果我不阻止线程,事情就会开始崩溃。现在,我想启动最多
throttle
线程,并且只在旧线程完成时启动新线程。我已经尝试了一些方法,但到目前为止都没有成功。我遇到的问题包括:

  • tasks
    集合必须完全填充所有任务,无论是活动的还是等待执行的,否则最后的
    .Wait()
    调用只查看它开始时的线程
  • 链接执行似乎需要使用
    Task.Run()
    或类似工具。但是我需要从一开始就引用每个任务,实例化一个任务似乎会自动启动它,这是我不想要的

如何执行此操作?

如果我理解正确,您可以启动
throttle
参数提到的有限数量的任务,并等待它们完成后再开始下一步

要在启动新任务之前等待所有已启动的任务完成,请使用以下实现

static async Task RunThreads(int totalThreads, int throttle)
{
    var tasks = new List<Task>();
    for (var n = 0; n < totalThreads; n++)
    {
        var task = DoSomething(n);
        tasks.Add(task);

        if (tasks.Count == throttle)
        {
            await Task.WhenAll(tasks);
            tasks.Clear();
        }
    }
    await Task.WhenAll(tasks); // wait for remaining
}
静态异步任务运行线程(inttotalthreads,intthrottle)
{
var tasks=新列表();
对于(var n=0;n
要在任务完成时添加任务,可以使用以下代码

static async Task RunThreads(int totalThreads, int throttle)
{
    var tasks = new List<Task>();
    for (var n = 0; n < totalThreads; n++)
    {
        var task = DoSomething(n);
        tasks.Add(task);

        if (tasks.Count == throttle)
        {
            var completed = await Task.WhenAny(tasks);
            tasks.Remove(completed);
        }
    }
    await Task.WhenAll(tasks); // all threads must complete
}
静态异步任务运行线程(inttotalthreads,intthrottle)
{
var tasks=新列表();
对于(var n=0;n
Stephen Toub在其文档中给出了以下节流示例

const int CONCURRENCY_LEVEL = 15;
Uri [] urls = …;
int nextIndex = 0;
var imageTasks = new List<Task<Bitmap>>();
while(nextIndex < CONCURRENCY_LEVEL && nextIndex < urls.Length)
{
    imageTasks.Add(GetBitmapAsync(urls[nextIndex]));
    nextIndex++;
}

while(imageTasks.Count > 0)
{
    try
    {
        Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);
        imageTasks.Remove(imageTask);

        Bitmap image = await imageTask;
        panel.AddImage(image);
    }
    catch(Exception exc) { Log(exc); }

    if (nextIndex < urls.Length)
    {
        imageTasks.Add(GetBitmapAsync(urls[nextIndex]));
        nextIndex++;
    }
}
const int并发级别=15;
Uri[]URL=…;
int-nextIndex=0;
var imageTasks=新列表();
while(nextIndex0)
{
尝试
{
Task imageTask=等待任务。WhenAny(imageTasks);
imageTasks.Remove(imageTask);
位图图像=等待图像任务;
面板。添加图像(图像);
}
catch(异常exc){Log(exc);}
if(nextIndex
微软的反应式扩展(Rx)——NuGet“Rx Main”——很好地解决了这个问题

只要这样做:

static void RunThreads(int totalThreads, int throttle) 
{
    Observable
        .Range(0, totalThreads)
        .Select(n => Observable.FromAsync(() => DoSomething(n)))
        .Merge(throttle)
        .Wait();
}

工作完成。

IMO最简单的选择是使用TPL数据流。您只需创建一个
ActionBLock
,按所需的并行度限制它,然后开始向其中发布项目。它确保在同一时间只运行一定数量的任务,当任务完成时,它开始执行下一项:

async Task RunAsync(int totalThreads, int throttle) 
{
    var block = new ActionBlock<int>(
        DoSomething,
        new ExecutionDataFlowOptions { MaxDegreeOfParallelism = throttle });

    for (var n = 0; n < totalThreads; n++)
    {
        block.Post(n);
    }

    block.Complete();
    await block.Completion;
}
异步任务RunAsync(inttotalthreads,intthrottle) { var block=新动作块( DoSomething, 新的ExecutionDataFlowOptions{MaxDegreeOfParallelism=throttle}); 对于(var n=0;n
以下是一些扩展方法的变体,以Sriram Sakthivel的答案为基础

在用法示例中,对
DoSomething
的调用被包装在显式转换闭包中,以允许传递参数

public static async Task RunMyThrottledTasks()
{
    var myArgsSource = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    await myArgsSource
        .Select(a => (Func<Task<object>>)(() => DoSomething(a)))
        .Throttle(2);
}

public static async Task<object> DoSomething(int arg)
{
    // Await some async calls that need arg..
    // ..then return result async Task..
    return new object();
}

public static async Task<IEnumerable<T>> Throttle<T>(IEnumerable<Func<Task<T>>> toRun, int throttleTo)
{
    var running = new List<Task<T>>(throttleTo);
    var completed = new List<Task<T>>(toRun.Count());
    foreach(var taskToRun in toRun)
    {
        running.Add(taskToRun());
        if(running.Count == throttleTo)
        {
            var comTask = await Task.WhenAny(running);
            running.Remove(comTask);
            completed.Add(comTask);
        }
    }
    return completed.Select(t => t.Result);
}

public static async Task Throttle(this IEnumerable<Func<Task>> toRun, int throttleTo)
{
    var running = new List<Task>(throttleTo);
    foreach(var taskToRun in toRun)
    {
        running.Add(taskToRun());
        if(running.Count == throttleTo)
        {
            var comTask = await Task.WhenAny(running);
            running.Remove(comTask);
        }
    }
}
公共静态异步任务RunMyThrottledTasks()
{
var myArgsSource=new[]{1,2,3,4,5,6,7,8,9};
等待我的资源
.选择(a=>(Func)(()=>DoSomething(a)))
.节流阀(2);
}
公共静态异步任务DoSomething(int arg)
{
//等待一些需要arg的异步调用。。
//..然后返回结果异步任务。。
返回新对象();
}
公共静态异步任务限制(IEnumerable toRun,int throttleTo)
{
var running=新列表(throttleTo);
var completed=新列表(toRun.Count());
foreach(toRun中的var taskToRun)
{
正在运行。添加(taskToRun());
if(running.Count==throttleTo)
{
var comTask=wait Task.WhenAny(正在运行);
正在运行。删除(comTask);
已完成。添加(comTask);
}
}
返回已完成。选择(t=>t.Result);
}
公共静态异步任务限制(此IEnumerable toRun,int throttleTo)
{
var running=新列表(throttleTo);
foreach(toRun中的var taskToRun)
{
正在运行。添加(taskToRun());
if(running.Count==throttleTo)
{
var comTask=wait Task.WhenAny(正在运行);
正在运行。删除(comTask);
}
}
}

首先,从线程中抽象出来。特别是因为您的操作是异步的,所以根本不应该考虑“线程”。在异步世界中,您有任务,与线程相比,您可以有大量的任务

可以使用
信号量lim
来限制异步代码:

static async Task DoSomething(int n);

static void RunConcurrently(int total, int throttle) 
{
  var mutex = new SemaphoreSlim(throttle);
  var tasks = Enumerable.Range(0, total).Select(async item =>
  {
    await mutex.WaitAsync();
    try { await DoSomething(item); }
    finally { mutex.Release(); }
  });
  Task.WhenAll(tasks).Wait();
}

您需要的是一个自定义任务调度程序。您可以从System.Threading.Tasks.TaskScheduler派生一个类,并实现两个主要函数
GetScheduledTasks()
QueueTask()
,以及其他函数以获得对限制任务的完全控制。这是一个有很好文档记录的例子


可能会在lis中添加大量的
节流
任务