继续运行特定数量的任务异步



我目前正在开发并发文件下载器。

出于这个原因,我想参数化并发任务的数量。我不想等待所有任务完成,而是保持运行相同的数字。

事实上,这个关于星星溢出的线程给了我一个适当的线索,但我正在努力使其异步:

继续运行特定数量的任务

这是我的代码:

public async Task StartAsync()
{
var semaphore = new SemaphoreSlim(1, _concurrentTransfers);
var queueHasMessages = true;
while (queueHasMessages)
{
try {
await Task.Run(async () =>
{
await semaphore.WaitAsync();
await asyncStuff();
});
}
finally {
semaphore.Release();
};
}
}

但是代码一次只执行一个。 我认为 await 阻止了我生成所需数量的任务,但我不知道如何在遵守信号量建立的限制的同时避免它。

如果我将所有任务添加到列表中并制作 whenall,信号量会引发异常,因为它已达到最大计数。

有什么建议吗?

我注意到,删除线解决方案将删除执行期间发生的任何异常。这很糟糕。

这是一个不会丢弃异常的解决方案:


Task.Run 是用于创建任务的工厂方法。您可以使用智能感知返回值检查自己。您可以将返回的任务分配到您喜欢的任何位置。

"等待"是一个运算符,它将等待它所操作的任务完成。您可以将任何任务与 await 运算符一起使用。

public static async Task RunTasksConcurrently()
{
IList<Task> tasks = new List<Task>();
for (int i = 1; i < 4; i++)
{
tasks.Add(RunNextTask());
}
foreach (var task in tasks) {
await task; 
}
}
public static async Task RunNextTask()
{
while(true) {
await Task.Delay(500);
}
}

通过将我们创建的任务的值添加到列表中,我们可以在稍后执行时等待它们。


上一页 答案如下

编辑:通过澄清,我想我理解得更好了。

您希望启动 3 个任务,而不是一次运行每个任务,一旦任务完成,就运行下一个任务。

我相信这可以使用.ContinueWith(Action<Task>)方法发生。

看看这是否更接近您的预期解决方案。

public void SpawnInitialTasks()
{
for (int i = 0; i < 3; i++)
{
RunNextTask();
}
}
public void RunNextTask()
{
Task.Run(async () => await Task.Delay(500))
.ContinueWith(t => RunNextTask());  
// Recurse here to keep running tasks whenever we finish one.
}

这个想法是,我们立即生成 3 个任务,然后每当一个任务完成时,我们就会生成下一个任务。如果需要保持数据在任务之间流动,可以使用参数:

RunNextTask(DataObject object)

您可以使用老式方式轻松执行此操作,而无需使用await通过使用Parallel.ForEach(),这允许您指定要使用的最大并发线程数。

例如:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
class Program
{
public static void Main(string[] args)
{
IEnumerable<string> filenames = Enumerable.Range(1, 100).Select(x => x.ToString());
Parallel.ForEach(
filenames,
new ParallelOptions { MaxDegreeOfParallelism = 4},
download
);
}
static void download(string filepath)
{
Console.WriteLine("Downloading " + filepath);
Thread.Sleep(1000); // Simulate downloading time.
Console.WriteLine("Downloaded " + filepath);
}
}
}

如果您运行此操作并观察输出,您将看到"文件"正在批量"下载"。

更好的模拟是更改download(),以便处理每个"文件"需要随机的时间,如下所示:

static Random rng = new Random();
static void download(string filepath)
{
Console.WriteLine("Downloading " + filepath);
Thread.Sleep(500 + rng.Next(1000)); // Simulate random downloading time.
Console.WriteLine("Downloaded " + filepath);
}

尝试一下,看看输出的差异。


但是,如果您想要一种更现代的方式来执行此操作,您可以查看 TPL(任务并行库)的Dataflow部分 - 这适用于async方法。

这要复杂得多,但它要强大得多。您可以使用ActionBlock来做到这一点,但描述如何做到这一点有点超出了我在这里给出的答案的范围。

看看StackOverflow上的另一个答案;它给出了一个简短的例子。

另请注意,TPL 未内置于 .Net 中 - 您必须从 NuGet 获取它。

最新更新