C# 如何使用带有C的线程启动进程#
问题:线程在启动引发异常的进程时被放弃 编辑 我正在重复这个问题以及我试图实现的目标,如何从线程开始一个进程 背景故事 我需要进程来运行exe的,如imagemagick,libreoffice。我正在尝试转换许多文件,然后将其结果附加到一个文件中。稍后将对状态文件进行进一步处理 我不擅长线程,我一直在引用一些关于stackoverflow的帖子,比如 我正在尝试这样做:C# 如何使用带有C的线程启动进程#,c#,multithreading,C#,Multithreading,问题:线程在启动引发异常的进程时被放弃 编辑 我正在重复这个问题以及我试图实现的目标,如何从线程开始一个进程 背景故事 我需要进程来运行exe的,如imagemagick,libreoffice。我正在尝试转换许多文件,然后将其结果附加到一个文件中。稍后将对状态文件进行进一步处理 我不擅长线程,我一直在引用一些关于stackoverflow的帖子,比如 我正在尝试这样做: foreach (Preset pr in listOfPreset) { Conv
foreach (Preset pr in listOfPreset)
{
ConvertRipper cRipper = new ConvertRipper(pr);
ThreadStart job = (new ThreadStart(()=> cRipper.Ripper()));
Thread th = new Thread(job);
th.Start();
}
public void Ripper()
{
//create the folders before converting
if (!Directory.Exists(preset.OutputFilePath))
Directory.CreateDirectory(preset.OutputFilePath);
Document document = CreateDocument(preset);
ProcessResult pr = v3Engine.Convert(Const.IMAGEMAGICK, v3Engine.ConvertImages(document));
if (pr.ExitCode == 0)
{
//write status to a file the xml status
}
}
现在,在Ripper方法中的某个地方,我确实有一个开始的过程,我基本上调用一个windows exe来转换一些文件
转换方法
Process proc = new Process();
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.Arguments = arguments;
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
proc.ErrorDataReceived += (sender, args) => error.Append(args.Data);
proc.OutputDataReceived += (sender, args) => output.Append(args.Data);
proc.Start(); *loc1*
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
proc.WaitForExit();
ProcessResult pr = new ProcessResult
{
StandardOutput = output.ToString(),
ErrorOutput = error.ToString(),
ExitCode = proc.ExitCode
};
proc.Close();
return pr;`
编辑
堆栈跟踪
“在System.Diagnostics.ProcessStartInfo.set\u重定向标准错误(布尔值
值)\r\n位于Conversion.Converter.AbstractConvertor.Convert(字符串
执行器,字符串参数)中
C:\Users\dev\source\repos\conversation\conversation\Converter\AbstractConvertor.cs:line
54“
*例外状态:**
无法计算表达式,因为代码已优化或
本机框架位于调用堆栈的顶部
在我的过程完成之后。我想将进程状态写入文件
事实上,我不明白它如何适合我的情况。因为,我已经在一个方法裂土器上使用了它,该裂土器在Convert方法中间接包含Process.start()
附言:我读过这样的评论:“当然,一个进程是在一个新进程中启动的(一组完全独立的线程),所以在一个线程上启动它是完全没有意义的。”
@Downvoter,你能分享一下我如何根据你的喜好改变这个问题吗
我觉得我的剧本需要它。如果没有,你能建议我还能做些什么吗
任何提示都将不胜感激。如果您对我的测试有任何疑问,请告诉我 您应该使用任何序列化程序(例如Json)序列化结果,然后将其写入文件:
if (pr.ExitCode == 0)
{
string json = JsonConvert.SerializeObject(pr);
File.WriteAllText("FileWithResult.txt", json);
}
如果有多个线程,则应使用不同的输出文件名。
或者,您可以使用任何记录器,例如NLog。一个快速而肮脏的解决方案:等待所有线程完成:
var threads = new List<Thread>();
foreach (Preset pr in listOfPreset)
{
ConvertRipper cRipper = new ConvertRipper(pr);
ThreadStart job = (new ThreadStart(()=> cRipper.Ripper()));
Thread th = new Thread(job);
th.Start();
threads.Add(th);
}
foreach (t in threads) t.Join();
var threads=newlist();
foreach(Preset列表中的预设pr)
{
ConvertRipper cRipper=新ConvertRipper(pr);
ThreadStart作业=(新ThreadStart(()=>cRipper.Ripper());
线程th=新线程(作业);
th.Start();
添加(th);
}
foreach(t in threads)t.Join();
这样线程就不会被丢弃
更优雅的解决方案包括启动进程但不启动任何线程的
任务
,它可以使用任务完成源代码
和进程来实现。退出
事件对您试图实现的目标使用线程没有意义,因为进程有自己的线程,因此,你试图在等式中引入线程,这让你自己的生活变得复杂
从本质上讲,您试图做的事情看起来像是线程,因为您希望东西异步运行,并等待它们的结果,然后再继续在程序中运行
为此,可以使用线程,让每个线程启动一个进程,然后让这些线程阻塞,直到进程退出,并将结果写入结果文件,让主线程等待所有其他线程完成,然后再继续程序的其余部分
Task
是一个很好的方法来完成这项任务,因为它为您隐藏了线程的所有不好之处(在大多数情况下)
您可以创建一个任务
数组,并为启动的每个流程创建一个任务
。在该任务中,您希望以process.WaitForExit()
结束该任务,它将阻止该任务,直到进程完成,然后您可以将进程结果写入该文件。使用数组跟踪您完成的所有任务
在创建和启动任务的循环之后,可以使用Task.WaitAll()
等待所有任务完成并继续执行程序的其余部分,因为这将阻塞主线程,直到每个任务完成
大概是这样的:
int processCount = 5; // or whatever
Task[] tasks = new Task[processCount];
for(int i = 0; i < processCount; i++)
{
Task t = new Task(() => {
ProcessStartInfo psi = ...
// start your process here
process.WaitForExit();
if (pr.ExitCode == 0)
{
//write status to a file the xml status
// remember to synchronize writes to the file so multiple tasks
// don't try to open the same file at the same time
// or just use different file names for each process
}
});
t.Start();
tasks[i] = t;
}
Task.WaitAll(tasks);
// continue with rest of program
这是一个优雅而酷的解决方案:使用任务而不旋转线程
请恕我直言,这有点乏味,我删除了所有与理解该想法无关的代码:
首先,我们有以下扩展类,它是创建任务的关键,这些任务执行流程时不会使线程旋转:
public static class Extensions
{
public static Task ExecuteAsync(this Process process)
{
var tcs = new TaskCompletionSource<bool>();
process.Exited += (sender, eventArgs) =>
{
tcs.TrySetResult(true);
};
try
{
process.Start();
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
return tcs.Task;
}
}
裂土器
也是异步的
:
static async Task Ripper()
{
// prepare convert arguments
ProcessResult result = await Convert();
// do something with the result
}
最后是主要方法:
var tasks = new List<Task>();
foreach (var item in list)
{
tasks.Add(Ripper());
}
Task.WaitAll(tasks.ToArray());
var tasks=newlist();
foreach(列表中的变量项)
{
添加(裂土器());
}
Task.WaitAll(tasks.ToArray());
希望有帮助。是的,您可能希望构造一个任务,该任务的完成由进程发出信号。已退出事件。如果Process类有一个WaitForExitAsync方法那就太好了,但是它不清楚你在问什么,你的帖子没有任何问题。你能显示抛出的异常吗?。请编辑您的问题并澄清。@JesúsLópez;我必须重新启动我的VS,我将很快共享异常信息。@BinoyCherian。请向ex.ToString()显示您在此处并不真正需要线程,因为您正在启动的进程已经在它们自己的线程上。这就是过程的工作原理。是否要等待所有进程完成后再写出状态?是否要在每个进程完成后立即写入进程的状态?这与问题无关,问题是关于使用线程启动进程。它不询问如何写入输出。@Logan。这个问题还不清楚,我还以为他在问如何解决这个问题
static async Task Ripper()
{
// prepare convert arguments
ProcessResult result = await Convert();
// do something with the result
}
var tasks = new List<Task>();
foreach (var item in list)
{
tasks.Add(Ripper());
}
Task.WaitAll(tasks.ToArray());