如何使用带有C#

时间:2018-10-26 14:05:18

标签: c# multithreading

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

编辑

我正在重复该问题以及我要实现的目标,即如何从线程启动进程。

背景故事

我需要运行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,请您分享一下我如何根据自己的喜好更改此问题。

我觉得我的情况需要它。如果没有,您能否建议我还能做些什么。

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

4 个答案:

答案 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,它启动进程但不增加任何线程,可以使用TaskCompletionSourceProcess.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。