繁体   English   中英

如何在 C# 中使用线程启动进程

[英]How to start a process using a thread with C#

问题:线程在启动进程抛出异常时被放弃。

编辑

我正在重复这个问题以及我试图实现的目标,如何从线程启动进程。

背景故事

我需要运行 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 方法的某个地方,我确实有一个启动的进程,我基本上调用了一个 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 value)\\r\\n at Conversion.Converter.AbstractConvertor.Convert(String executor, String arguments) in C:\\Users\\dev\\source\\repos\\Converstion\\Converstion\\ Converter\\AbstractConvertor.cs:line 54"

*异常状态:**

由于代码已优化或本机帧位于调用堆栈顶部,因此无法计算表达式。

在我的过程完成后。 我想将进程状态写入文件。

示例中,我不明白它如何适合我的情况。 因为,我已经在一个方法 Ripper 上使用了它,该方法间接地将 Process.start() 包含在 Convert 方法中;

PS:我读过诸如“当然,一个进程在一个新进程(一组完全独立的线程)中启动,因此在一个线程上启动它是完全没有意义的。”之类的评论。

@Downvoter,能否请您分享我如何根据您的喜好更改此问题。

我觉得我的场景需要它。 如果没有,你能建议我还能做什么。

任何提示将不胜感激。 如果您对我的测试有任何疑问,请告诉我。

这是一个优雅而酷的解决方案:在不启动线程的情况下使用任务。

请持保留态度,这有点乏味,我删除了所有与理解这个想法无关的代码:

首先我们有以下扩展类,它是创建不启动线程执行进程的任务的关键:

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());

希望能帮助到你。

一个快速而肮脏的解决方案:等待所有线程完成:

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是启动一个进程,但不旋转起来任何线程,它可以采用以下方式实现TaskCompletionSourceProcess.Exited事件

将线程用于您想要实现的目标是没有意义的,因为进程有自己的线程,因此您试图将线程引入等式,从而使自己的生活复杂化。

本质上,您尝试做的事情看起来像线程,因为您希望东西异步运行并在继续执行程序之前等待它们的结果。

可以为此目的使用线程,让每个线程触发一个进程,然后让这些线程阻塞,直到进程退出并将结果写入您的结果文件,并让您的主线程在继续执行之前等待所有其他线程完成你的程序的其余部分。

Task是完成此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事件。 在这种情况下,您可以保留一个布尔数组,指示您的每个进程是否已退出,然后在循环中休眠,直到数组中的每个值都为真以继续您的程序。

更新:正如@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;
}

您应该使用任何序列化程序(例如 Json)序列化结果,然后将其写入文件:

if (pr.ExitCode == 0)
{
   string json = JsonConvert.SerializeObject(pr);
   File.WriteAllText("FileWithResult.txt", json);
}

如果您有多个线程,则应使用不同名称的输出文件。 或者您可以使用任何记录器,例如 NLog。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM