我正在使用文件名处理队列。每个文件都必须由外部二进制文件处理。这工作正常,但它一次只处理一个文件。是否有可能两个并行产生多个进程?
Queue<string> queue = new Queue<string>();
queue.Enqueue("1.mp3");
queue.Enqueue("2.mp3");
queue.Enqueue("3.mp3");
...
queue.Enqueue("10000.mp3");
while (queue.Count > 0)
{
string file = queue.Dequeue();
Process p = new Process();
p.StartInfo.FileName = @"binary.exe";
p.StartInfo.Arguments = file;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
p.WaitForExit();
}
更新:我喜欢Alex LE的解决方案(Spawn流程,但一次只有5个),但是可以按照Ben Voigt的建议等待子进程退出吗?
编辑1:我需要检查p.ExitCode == 0以进行一些数据库更新。
答案 0 :(得分:3)
如果与流程相关联的等待句柄标记为“公共”而非“内部”(如当前为(vote here to ask Microsoft to change that)),那么本应该是可能的:
void BatchProcess()
{
Queue<string> queue = new Queue<string>();
queue.Enqueue("1.mp3");
queue.Enqueue("2.mp3");
queue.Enqueue("3.mp3");
...
queue.Enqueue("10000.mp3");
WaitHandle[] subprocesses = new WaitHandle[Math.Min(queue.Count, 5)];
for( int i = 0; i < subprocesses.Length; i++ ) {
subprocesses[i] = Spawn(queue.Dequeue());
}
while (queue.Count > 0) {
int j = WaitHandle.WaitAny(subprocesses);
subprocesses[j].Dispose();
subprocesses[j] = Spawn(queue.Dequeue());
}
WaitHandle.WaitAll(subprocesses);
foreach (var wh in subprocesses) {
wh.Dispose();
}
}
ProcessWaitHandle Spawn(string args)
{
using (Process p = new Process()) {
p.StartInfo.FileName = @"binary.exe";
p.StartInfo.Arguments = args;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.Start();
return p.WaitHandle;
}
}
这将是最有效的解决方案,因为除了Win32进程本身之外不需要同步对象。 C#代码中不需要额外的线程,也没有异步方法调用,因此无需任何锁定或其他同步。
答案 1 :(得分:1)
提取代码的某些部分并添加信号量:
Semaphore semX = new Semaphore(5, int.MaxValue);
void f(name, args) {
using (Process p = new Process())
{
p.StartInfo.FileName = name;
p.StartInfo.Arguments = args;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
p.WaitForExit();
}
semX.Release(); // !!! This one is important
}
然后使用
while (queue.Count > 0)
{
string file = queue.Dequeue();
semX.WaitOne(); // !!!
(new Thread((ThreadStart) (() => f(file, "")))).Start(); // dirty unreadable code to start a routine async
}
for (int n = 5; n > 0; n--) // Wait for the last 5 to finish
semX.WaitOne();
semX.Dispose(); // Dispose the semaphore
答案 2 :(得分:1)
这有效(使用C#5.0 async await会更容易):
Queue<string> queue = new Queue<string>();
queue.Enqueue("1.mp3");
queue.Enqueue("2.mp3");
queue.Enqueue("3.mp3");
...
queue.Enqueue("10000.mp3");
int runningProcesses = 0;
const int MaxRunningProcesses = 5;
object syncLock = new object();
Action<string> run = new Action<string>(delegate(string file) {
using (Process p = new Process()) {
p.StartInfo.FileName = @"binary.exe";
p.StartInfo.Arguments = file;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
p.WaitForExit();
}
});
Action runNext = null;
runNext = delegate() {
lock (syncLock) {
if (queue.Count > 0) {
run.BeginInvoke(queue.Dequeue(), new AsyncCallback(delegate {
runNext();
}), null);
}
}
};
while (runningProcesses++ < MaxRunningProcesses) {
runNext();
}
答案 3 :(得分:0)
您可以为此使用信号量,并根据需要异步调用长时间运行的进程:
private Semaphore _semaphore;
private delegate void Processor(string fileName);
[Test]
public void SetterTest() {
var queue = new Queue<string>();
queue.Enqueue("1.mp3");
queue.Enqueue("2.mp3");
queue.Enqueue("3.mp3");
// ..
queue.Enqueue("10000.mp3");
var noOfThreads = 5;
using (_semaphore = new Semaphore(noOfThreads, noOfThreads)) {
while (queue.Count > 0) {
string fileName;
fileName = queue.Dequeue();
_semaphore.WaitOne();
new Processor(ProcessFile).BeginInvoke(fileName, null, null);
}
for (int i=0; i<noOfThreads; i++) _semaphore.WaitOne();
}
}
private void ProcessFile(string file) {
Process p;
using (p = new Process()) {
p.StartInfo.FileName = @"binary.exe";
p.StartInfo.Arguments = file;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
p.WaitForExit();
}
_semaphore.Release();
}
希望这会有所帮助
答案 4 :(得分:0)
基本上你有生产者消费者的问题。所以你绝对应该使用System.Collections.Concurrent命名空间中的集合。这是一个简单的例子,您可以简单地应用于您的问题 - 作为额外的奖励,您可以同时开始填充队列及其处理!
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
class Program
{
static readonly BlockingCollection<string> _collection = new BlockingCollection<string>();
static void Main()
{
const int maxTasks = 5;
var tasks = new List<Task> {
// startup publisher task...
Task.Factory.StartNew(() => {
for(var i = 0; i < 1000; i++)
{
_collection.Add(i + ".mp3");
}
Console.WriteLine("Publisher finished");
_collection.CompleteAdding();
}),
};
for (var i = 0; i < maxTasks; i++)
{
tasks.Add(Task.Factory.StartNew(ConsumerTask(i)));
}
Task.WaitAll(tasks.ToArray()); // wait for completion
}
static Action ConsumerTask(int id)
{
// return a closure just so the id can get passed
return () =>
{
string item;
while (true)
{
if (_collection.TryTake(out item, -1))
{
using(Process p = new Process())
{
p.StartInfo.FileName = "binary.exe";
p.StartInfo.Arguments = item;
p.Start();
p.WaitForExit();
var exitCode = p.ExitCode;
// TODO handle exit code
}
}
else if (_collection.IsAddingCompleted)
{
break; // exit loop
}
}
Console.WriteLine("Consumer {0} finished", id);
};
}
}
答案 5 :(得分:0)
这可以阻止主线程部分基于Ben的答案,但这已经运行了。
static void Run(string file)
{
using (Process p = new Process()) {
p.StartInfo.FileName = @"binary.exe";
p.StartInfo.Arguments = file;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.Start();
p.WaitForExit();
}
}
static WaitHandle RunAsync(string file)
{
Action<string> result = new Action<string>(Run).BeginInvoke(file, null, null);
return result.AsyncWaitHandle;
}
static void Main()
{
Queue<string> queue = new Queue<string>();
queue.Enqueue("1.mp3");
queue.Enqueue("2.mp3");
queue.Enqueue("3.mp3");
queue.Enqueue("4.mp3");
queue.Enqueue("5.mp3");
queue.Enqueue("6.mp3");
// ...
queue.Enqueue("10000.mp3");
const int MaxRunningProcesses = 5;
List<WaitHandle> runningProcesses = new List<WaitHandle>(MaxRunningProcesses);
while (queue.Count > 0 && runningProcesses.Count < MaxRunningProcesses) {
runningProcesses.Add(RunAsync(queue.Dequeue()));
}
while (runningProcesses.Count > 0) {
int j = WaitHandle.WaitAny(runningProcesses.ToArray());
runningProcesses[j].Close();
runningProcesses.RemoveAt(j);
if (queue.Count > 0) {
runningProcesses.Add(RunAsync(queue.Dequeue()));
}
}
}