我目前正在开发并发文件下载器。
出于这个原因,我想参数化并发任务的数量。我不想等待所有任务完成,而是保持运行相同的数字。
事实上,这个关于星星溢出的线程给了我一个适当的线索,但我正在努力使其异步:
继续运行特定数量的任务
这是我的代码:
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 获取它。