我只是想知道是否有人能指出我关于async / await框架和线程池的正确方向?
基本上,我尝试做的是在单独的线程/异步中执行x个操作,但是最多只有y个线程。
例如,让我们说我有100个数据库操作:
less huge-file.txt
我想要做的是有一些方法一次运行10个这样的操作(理想情况是每个单独的线程,所以10个线程),并且每个完成后,下一个开始在线程上然后变得可用。然后我们等待所有操作完成并完成所有线程...
这是否可以轻松实现而不需要太多努力或增加大量复杂性?
答案 0 :(得分:14)
我认为你通过关注线程来忽略这一点,特别是对于不需要线程执行的异步操作。
.NET有一个很棒的ThreadPool
你可以使用。你不知道其中有多少线程,你不在乎。它只是工作(直到它没有,你需要自己配置它,但这是非常先进的。)
在ThreadPool
上运行任务非常简单。为每个操作创建任务并使用SemaphoreSlim
限制它们或使用现成的TPL数据流块。例如:
var block = new ActionBlock<SomeData>(
_ => _repository.WriteDataAsync(_), // What to do on each item
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 10 }); // How many items at the same time
foreach (var item in items)
{
block.Post(item); // Post all items to the block
}
block.Complete(); // Signal completion
await block.Completion; // Asynchronously wait for completion.
但是,如果您计划创建&#34;专用&#34;线程可以使用Task.Factory.StartNew
LongRunning
选项在ThreadPool
之外创建专用线程。但请记住,异步操作在整个操作过程中不会保持相同的线程,因为异步操作不需要线程。因此,从专用线程开始可能毫无意义(在我的博客上更多内容:LongRunning Is Useless For Task.Run With async-await)
答案 1 :(得分:6)
@ i3arnon的回答是正确的。使用TPL数据流。
本答复的其余部分仅用于教育目的和/或特殊用例。
我最近在一个项目中遇到了类似的问题,我无法引入任何外部依赖项,所以我不得不推出自己的负载平衡实现,结果非常简单(直到你开始接线取消和有序的结果 - 但这超出了这个问题的范围。)
我无视“10个专用线程”的要求,因为正如其他人已经解释的那样,在处理异步操作时没有意义。相反,我将维持最多N
个并发Task
个实例来处理工作量。
static async Task InvokeAsync(IEnumerable<Func<Task>> taskFactories, int maxDegreeOfParallelism)
{
Queue<Func<Task>> queue = new Queue<Func<Task>>(taskFactories);
if (queue.Count == 0) {
return;
}
List<Task> tasksInFlight = new List<Task>(maxDegreeOfParallelism);
do
{
while (tasksInFlight.Count < maxDegreeOfParallelism && queue.Count != 0)
{
Func<Task> taskFactory = queue.Dequeue();
tasksInFlight.Add(taskFactory());
}
Task completedTask = await Task.WhenAny(tasksInFlight).ConfigureAwait(false);
// Propagate exceptions. In-flight tasks will be abandoned if this throws.
await completedTask.ConfigureAwait(false);
tasksInFlight.Remove(completedTask);
}
while (queue.Count != 0 || tasksInFlight.Count != 0);
}
用法:
Func<Task>[] taskFactories = {
() => _repository.WriteData(someData1),
() => _repository.WriteData(someData2),
() => _repository.WriteData(someData3),
() => _repository.WriteData(someData4)
};
await InvokeAsync(taskFactories, maxDegreeOfParallelism: 2);
......或
IEnumerable<SomeData> someDataCollection = ... // Get data.
await ParallelTasks.InvokeAsync(
someDataCollection.Select(someData => new Func<Task>(() => _repository.WriteData(someData))),
maxDegreeOfParallelism: 10
);
此解决方案不会遇到负载均衡问题,这种问题在任务持续时间不同且输入已预分区(例如Yoda conditions)的情况下常见于其他简单实现中。
带有优化和参数验证的版本:this one。