c# - C# 使用多线程递归扫描树
问题描述
我正在扫描一些目录中的项目。我刚刚阅读了 C# 问题中的多线程目录循环,但我仍然想让它成为多威胁的。尽管每个人都说驱动器将成为瓶颈,但我有一些观点:
- 驱动器可能大多是“单线程”的,但你怎么知道它们将来会带来什么?
- 您如何知道您正在扫描的不同子路径是同一个物理驱动器?
- 我在上面使用了一个抽象层(甚至两个),
System.IO
以便以后可以在不同的场景中重用代码。
所以,我的第一个想法是使用Task,第一个虚拟实现是这样的:
public async Task Scan(bool recursive = false) {
var t = new Task(() => {
foreach (var p in path.scan) Add(p);
if (!recursive) return;
var tks = new Task[subs.Count]; var i = 0;
foreach (var s in subs) tks[i++] = s.Scan(true);
Task.WaitAll(tks);
}); t.Start();
await t;
}
我不喜欢Task
为每个项目创建一个的想法,通常这似乎并不理想,但这只是为了测试,因为任务被宣传为自动管理线程......
这种方法有效,但速度很慢。完成需要5s以上,而下面的单个威胁版本需要大约0.5s才能在同一数据集上完成整个程序:
public void Scan2(bool recursive = false) {
foreach (var p in path.scan) Add(p);
if (!recursive) return;
foreach (var s in subs) s.Scan2(true);
}
我徘徊在拳头方法真正出了什么问题。机器未加载,CUP 使用量微不足道,驱动器很好......我尝试使用 NProfiler 对其进行分析,除了程序Task.WaitAll(tks)
一直在运行之外,它并没有告诉我太多。
我还编写了一个线程锁定计数机制,在添加每个项目期间调用该机制。也许是它的问题?
#region SubCouting
public Dictionary<Type, int> counters = new Dictionary<Type, int>();
private object cLock = new object();
private int _sc = 0;
public int subCount => _sc;
private void inCounter(Type t) {
lock (cLock) {
if (!counters.ContainsKey(t)) counters.Add(t, 1);
counters[t]++;
_sc++;
}
if (parent) parent.inCounter(t);
}
#endregion
但是即使线程在这里等待,执行时间会不会类似于单线程版本而不是慢 10 倍?
我不知道如何解决这个问题。如果我不想使用任务,是否需要手动管理线程,或者是否已经有一些库非常适合这项工作?
解决方案
我想你几乎明白了。Task.WaitAll(tks)
是问题所在。您为此阻塞一个线程,因为这是同步操作。您很快就会退出线程,所有线程都在等待一些没有线程可以运行的任务。您可以使用异步解决此问题,将等待替换为await Task.WhenAll(...)
. 它会在等待时释放线程。对于一些工作负载,多线程版本明显更快。当只有 IO 绑定时,它大致相等。
ConcurrentBag<string> result = new ConcurrentBag<string>();
List<string> result2 = new List<string>();
public async Task Scan(string path)
{
await Task.Run(async () =>
{
var subs = Directory.GetDirectories(path);
await Task.WhenAll(subs.Select(s => Scan(s)));
result.Add(Enumerable.Range(0, 1000000).Sum(i => path[i % path.Length]).ToString());
});
}
public void Scan2(string path)
{
result2.Add(Enumerable.Range(0, 1000000).Sum(i => path[i % path.Length]).ToString());
var subs = Directory.GetDirectories(path);
foreach (var s in subs) Scan2(s);
}
private async void button4_Click(object sender, EventArgs e)
{
string dir = @"d:\tmp";
System.Diagnostics.Stopwatch st = new System.Diagnostics.Stopwatch();
st.Start();
await Scan(dir);
st.Stop();
MessageBox.Show(st.ElapsedMilliseconds.ToString());
st = new System.Diagnostics.Stopwatch();
st.Start();
Scan2(dir);
st.Stop();
MessageBox.Show(st.ElapsedMilliseconds.ToString());
MessageBox.Show(result.OrderBy(x => x).SequenceEqual(result2.OrderBy(x => x)) ? "OK" : "ERROR");
}
推荐阅读
- android - 如何从自定义 Listview 中的 AlertDialog 获取数据?
- spring - 我可以在 mybatis mapper 上设置 Spring AOP 切入点吗?
- node.js - 给定 node.js 版本,以编程方式确定哪些 npm 版本兼容
- html - 如何使我的文本在 flex 框下的间距相同的情况下保持对齐
- mysql - 如何对 MySQL 中的数据进行非规范化处理?
- vb.net - access数据库中更新sql语句出错[VB2010]
- typescript - Typescript:函数式 Monad 构造中的复杂自引用泛型类型,它在哪里工作,在哪里失败
- html - 如何在不破坏 flex 布局的情况下在 div 的右侧显示图像?
- python - 如何在 Python 中实现嵌套的 while 循环
- python - setattr if hasattr else setattr on self.item