我正在写一个简单的应用程序(对于我的妻子不低于:-P),它可以为大量图像做一些图像处理(调整大小,时间戳等)。所以我正在编写一个可以同步和异步执行此操作的库。我决定使用Event-based Asynchronous Pattern。当使用这种模式时,您需要在工作完成时提出事件。这是我知道何时完成的问题。所以基本上,我DownsizeAsync方法我在做这样的事情(缩编图像异步方法):让多个线程工作并等待他们全部完成的最佳方式是什么?
public void DownsizeAsync(string[] files, string destination)
{
foreach (var name in files)
{
string temp = name; //countering the closure issue
ThreadPool.QueueUserWorkItem(f =>
{
string newFileName = this.DownsizeImage(temp, destination);
this.OnImageResized(newFileName);
});
}
}
都完成时,他们最棘手的部分,现在是知道。
下面是我考虑过的:使用像这里的ManualResetEvents:http://msdn.microsoft.com/en-us/library/3dasc8as%28VS.80%29.aspx但我遇到的问题是,你只能等待64或更少的事件。我可能有许多更多的图像。
第二个选项:有一个统计已完成的图像的计数器,并引发该事件当数达到总:
public void DownsizeAsync(string[] files, string destination)
{
foreach (var name in files)
{
string temp = name; //countering the closure issue
ThreadPool.QueueUserWorkItem(f =>
{
string newFileName = this.DownsizeImage(temp, destination);
this.OnImageResized(newFileName);
total++;
if (total == files.Length)
{
this.OnDownsizeCompleted(new AsyncCompletedEventArgs(null, false, null));
}
});
}
}
private volatile int total = 0;
现在这种感觉“哈克”,我不能完全肯定如果这是线程安全的。
所以,我的问题是,这样做的最好方法是什么?是否有另一种方法来同步所有线程?我应该不使用ThreadPool吗?谢谢!!
UPDATE基于评价和从几个答案,我决定采取这种方式反馈:
首先,我创建了一个批处理到枚举“批”的扩展方法:
public static IEnumerable<IEnumerable<T>> GetBatches<T>(this IEnumerable<T> source, int batchCount)
{
for (IEnumerable<T> s = source; s.Any(); s = s.Skip(batchCount))
{
yield return s.Take(batchCount);
}
}
基本上,如果你做这样的事情:
foreach (IEnumerable<int> batch in Enumerable.Range(1, 95).GetBatches(10))
{
foreach (int i in batch)
{
Console.Write("{0} ", i);
}
Console.WriteLine();
}
你得到这样的输出:
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95
这个想法是(作为评论中的某些人指出),没有必要为每个图像创建一个单独的线程。因此,我会将图像批量加入[machine.cores * 2]。然后,我将使用我的第二种方法,这只是为了让柜台继续前进,当柜台达到我期待的总数时,我就知道我已经完成了。
我之所以现在,它实际上是线程安全的确信,是因为我已标记的总变量作为挥发性它根据MSDN:
volatile修饰符通常用于 了字段,通过 多线程访问,而不使用 锁语句来序列化访问。 使用volatile修饰符确保 一个线程检索最 跟上时代的价值被另一个线程 书面
意味着我应该在明确的(如果不是,请让我知道!)
因此,这里是我跟去的代码:
public void DownsizeAsync(string[] files, string destination)
{
int cores = Environment.ProcessorCount * 2;
int batchAmount = files.Length/cores;
foreach (var batch in files.GetBatches(batchAmount))
{
var temp = batch.ToList(); //counter closure issue
ThreadPool.QueueUserWorkItem(b =>
{
foreach (var item in temp)
{
string newFileName = this.DownsizeImage(item, destination);
this.OnImageResized(newFileName);
total++;
if (total == files.Length)
{
this.OnDownsizeCompleted(new AsyncCompletedEventArgs(null, false, null));
}
}
});
}
}
我接受反馈,我绝不是在多线程方面的专家,因此,如果有人看到任何问题与此,或有一个更好的主意,请让我知道。 (是的,这只是一个自制的应用程序,但我对如何使用我在这里获得的知识来改进我们在工作中使用的搜索/索引服务有一些想法。)现在,我会将此问题保持开放直到我感觉我正在使用正确的方法。谢谢大家的帮助。
总++看起来没有线程安全! – RichardOD 2009-12-16 15:05:30
计数器方法具有很高的可扩展性。只需使用Interlocked.Increment和.Decrement使其线程安全。 – 2009-12-16 15:39:42
实际上,64是这个限制吗?即使使用64个物理内核,您也会遇到内存和磁盘瓶颈问题,并行访问会降低速度。但是,这些可能会在一两年内消失。 – peterchen 2009-12-16 15:54:57