问题:线程在启动进程引发异常时被放弃。
编辑
我正在重复该问题以及我要实现的目标,即如何从线程启动进程。
背景故事
我需要运行exe的过程,例如imagemagick,libreoffice。我正在尝试转换许多文件,然后将其结果附加到文件中。以后还要对状态文件做进一步的处理。
我不擅长线程处理,我一直在引用一些关于stackoverflow的帖子,例如this。
我正在尝试做这样的事情:
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方法内部的某个地方,我确实有一个可以启动的Process,我基本上是调用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_RedirectStandardError(Boolean 值)\ r \ n在Conversion.Converter.AbstractConvertor.Convert(String 执行程序,字符串参数)在 C:\ Users \ dev \ source \ repos \ Converstion \ Converstion \ Converter \ AbstractConvertor.cs:line 54“
*异常状态:**
由于代码已优化或 本机框架位于调用堆栈的顶部。
我的过程完成后。我想将过程状态写入文件。
在example中,我不了解它如何适合我的情况。因为,我已经在Ripper方法上使用了此方法,该方法将Process.start()间接包含在Convert方法中;
P.S:我读过一些评论,例如“当然,一个进程是在一个新进程(一个完全独立的线程)中启动的,因此在线程上启动它是完全没有意义的。”
@Downvoter,请您分享一下我如何根据自己的喜好更改此问题。
我觉得我的情况需要它。如果没有,您能否建议我还能做些什么。
任何提示将不胜感激。如果您对我的测试有任何疑问,请告诉我。
答案 0 :(得分:1)
这是一种优雅而酷的解决方案:使用任务而无需增加线程。
请带着一点盐,这有点平淡,我删除了所有与理解这个想法无关的代码:
首先我们有以下扩展类,这是创建执行任务而无需增加线程的任务的关键:
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;
}
}
然后我们有了Convert
方法,它现在是一个async
函数:
static async Task<ProcessResult> Convert()
{
var proces = new Process();
// Configure process
// .......
// End configure process
await proces.ExecuteAsync();
return new ProcessResult
{
ExitCode = proces.ExitCode,
// Set other properties
};
}
Ripper
也是async
:
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());
希望有帮助。
答案 1 :(得分:0)
一种快速而肮脏的解决方案:等待所有线程完成:
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();
这样线程就不会被抛弃。
更优雅的解决方案涉及Task
,它启动进程但不增加任何线程,可以使用TaskCompletionSource
和Process.Exited
事件来实现
答案 2 :(得分:0)
将线程用于您要实现的目标是没有意义的,因为进程具有自己的线程,因此,通过尝试将线程引入方程式来使自己的生活变得复杂。
基本上,您要尝试执行的操作看起来像线程,因为您希望东西异步运行并等待它们的结果,然后再继续执行程序。
您可以为此目的使用线程,使每个线程触发一个进程,然后阻塞这些线程,直到该进程退出并将结果写入结果文件,并等待主线程在继续执行程序的其余部分之前使所有其他线程完成。
Task
是实现此目的的一种非常不错的方法,因为它为您(在大多数情况下)隐藏了所有线程化的麻烦。
您可以制作一个Task
的数组,并为启动的每个进程制作一个Task
。在该任务中,您想以process.WaitForExit()
结尾,它将阻塞任务,直到过程完成为止,然后您可以将过程结果写入文件中。使用数组来跟踪您制作的所有Task
。
在创建和启动任务的循环之后,可以使用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
另一种替代方法是利用Process.Exited
事件。在那种情况下,您可以保留一个布尔数组来指示您的每个进程是否已经退出,然后在循环中休眠直到该数组中的每个值都为true才能继续执行程序。
更新:根据@Jesús López的建议,您还可以如下使用TaskCompletionSource
:
int processCount = 5; // or whatever
Task[] tasks = new Task[processCount];
for(int i = 0; i < processCount; i++)
{
TaskCompletionSource<int> taskCompletionSource = new TaskCompletionSource<int>();
ProcessStartInfo psi = new ProcessStartInfo();
var process = new Process();
process.StartInfo = psi;
process.Exited += (obj, args) =>
{
// write to file here
taskCompletionSource.SetResult(process.ExitCode);
};
process.Start();
tasks[i] = taskCompletionSource.Task;
}
答案 3 :(得分:-2)
您应该使用任何序列化程序(例如Json)序列化结果,然后将其写入文件:
select REGEXP_SUBSTR('a|b|c|2|:x80|3|rr|','([^|]+\|){3}(.+)$',1,1,null,2) from dual
如果线程很多,则应使用不同名称的输出文件。 或者,您可以使用任何记录器,例如NLog。