-1

The Problem : The thread gets abandoned when it starts the process throwing an exception.

EDIT

I'm re-iterating the question along with the thing I'm trying to achieve, How do I start a process from a thread.

Background story

I need the process to run exe's such as imagemagick, libreoffice. I am trying to convert many files and then append their results to a file. There is further processing to be done on the status file later.

I'm not good at threading and I have been referring to some posts on stackoverflow such as this.

I am trying to do something like 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
        }
    }

Now somewhere inside the Ripper method I do have a Process which gets started, I basically call a windows exe to convert some files

Convert method

    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;`

EDIT

The stacktrace " at 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"

*The exception state:**

Unable to evaluate expression because the code is optimized or a native frame is on top of the call stack.

After my process completes. I would like to write the process status to a file.

In the example , I don't understand how it fits into my situation. Because, I have already used this on a method Ripper that indirectly houses the Process.start() inside the Convert method;

P.S: I have read comments such as "Of course, a process starts in a new process (a completely separate bunch of threads), so starting it on a thread is completely pointless."

@Downvoter, could you please share how I could change this question to your liking.

I feel like my scenario needs it. If not, could you suggest what else could I possibly do.

Any tips would be appreciated. Please let me know if you have any questions regarding my tests.

Binoy Cherian
  • 364
  • 1
  • 6
  • 23
  • Yes, you probably want to construct a task, whose completion is signaled by the Process.Exited event. It would be great if the Process class had a WaitForExitAsync method, but it doesn' – Flydog57 Oct 26 '18 at 14:15
  • 1
    It's unclear what you are asking, your post doesn't have any questions. Could you show the thrown exception?. Please edit your question and clarify. – Jesús López Oct 26 '18 at 14:29
  • @JesúsLópez;I had to restart my VS, I will share the exception info soon. – Binoy Cherian Oct 26 '18 at 14:31
  • @BinoyCherian. Please show ex.ToString() – Jesús López Oct 26 '18 at 14:32
  • You don't really need threads here because the processes you are starting are already on their own threads. That's how processes work. Do you want to wait for all the processes to complete before writing out the status? Do you want to write the status of the process immediately after each process finishes? – ldam Oct 26 '18 at 14:34
  • @Logan, i would like to write it immediately. – Binoy Cherian Oct 26 '18 at 14:38
  • Ok, so then your only roadblock I can think of is trying to write to the same file if more than one process exits at the same time. I'll type up an answer for you. – ldam Oct 26 '18 at 14:40
  • @JesúsLópez: That's the string you were looking for "System.Threading.ThreadAbortException: Le thread a été abandonné.\r\n à System.Diagnostics.ProcessStartInfo.set_RedirectStandardError(Boolean value)\r\n à Conversion.Converter.AbstractConvertor.Convert(String executor, String arguments) dans C:\\Users\\dev\\source\\repos\\Converstion\\Converstion\\Converter\\AbstractConvertor.cs:ligne 54" – Binoy Cherian Oct 26 '18 at 14:46

4 Answers4

1

Here is the elegant and cool solution: using tasks without spinning up threads.

Please take this with a grain of salt, it's a little insipid, I removed all code that is not relevant to understand the idea:

First we have the following extension class, it is the key to create tasks that execute processes without spinning up threads:

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;
    }
}

Then we have the Convert method, It is now an async function:

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 is also async:

static async Task Ripper()
{
    // prepare convert arguments
   ProcessResult result = await Convert();
   // do something with the result
}

And finally the main method:

var tasks = new List<Task>();
foreach (var item in list)
{
    tasks.Add(Ripper());
}
Task.WaitAll(tasks.ToArray());

Hope it helps.

Jesús López
  • 8,338
  • 7
  • 40
  • 66
0

A quick and dirty solution: wait for all threads to finish:

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

This way threads don't get abandoned.

A more elegant solution involves a Task that starts a process but doesn't spin up any thread, it can be implemented using TaskCompletionSource and Process.Exited event

Jesús López
  • 8,338
  • 7
  • 40
  • 66
0

Using threads for what you're trying to achieve doesn't make sense because processes have their own threads, so you're complicating your own life by trying to introduce threading into the equation.

Essentially what you're trying to do looks like threading because you want stuff to run asynchronously and wait for their results before continuing in your program.

You could use threads for this purpose, making each thread fire off a process and then having those threads block until the process exits and writing the result to your result file, and having your main thread waiting for all other threads to complete before carrying on the rest of your program.

Task is a pretty nice way to accomplish this because it hides away all the nastiness of threading for you (for the most part).

You can make an array of Tasks, and make a Task for each process that you start. In that task you want to end it with process.WaitForExit() which will block the task until the process completes, and after that you can write your process results to the file. Use the array to keep track of all the Tasks you've made.

After your loop of creating and starting the tasks, you can use Task.WaitAll() to wait for all the tasks to complete and carry on with the rest of the program, as that will block the main thread until each task completes.

Something like this:

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

Another alternative is making use of the Process.Exited event. In that case you could keep an array of bools indicating whether each of your processes has exited, and then sleeping in a loop until every value in the array is true to carry on your program.

Update: As @Jesús López suggests, you can also make use of a TaskCompletionSource as follows:

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;
}
ldam
  • 4,412
  • 6
  • 45
  • 76
-2

You should serialize result using any serializer for example Json and then write it to file:

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

If you have many threads you should use different names of output files. Or you can use any logger for example NLog.