0

I want the method PrintOrderAsync to execute an external EXE to print an order. The code for method PrintOrderAsync does not show any syntax errors

public async Task<string> PrintOrderAsync(string PrintExe, string ExePath, int nOrderNo)
{
    await Task.Factory.StartNew(() => Process.Start(ExePath + PrintExe, nOrderNo.ToString()));

        return "";
 }

But I am struggling with the syntax for the calling method. Here is what I tried:

Task<string> result = PrintOrderAsync(PrintExe, ExePath, nOrderNo);

And I see syntax error on the above line. What am I missing?

Hidalgo
  • 941
  • 2
  • 14
  • 38
  • 1
    Why would you choose to use `StartNew`? Did you see that as an example somewhere? – Stephen Cleary Apr 11 '16 at 00:18
  • @StephenCleary Yes, I did see this somewhere. But I am listening and if you think the code should work better with changes, please let me know. – Hidalgo Apr 11 '16 at 00:24
  • @StephenCleary Now that you asked the question, I am trying to see how I can change/optimize the line with StartNew and cannot figure this out. Obviously you see something wrong with my syntax so I need to learn how to make it work better. – Hidalgo Apr 11 '16 at 00:43
  • @Hidalgo, are you really happy with fire-and-forget semantics of `Process.Start`? Any particular reason you're not calling `WaitForExit` on the returned `Process` instance? – Kirill Shlenskiy Apr 11 '16 at 02:32
  • Look at the answers in [this](http://stackoverflow.com/questions/10788982/is-there-any-async-equivalent-of-process-start) question using TaskCompletionSource for a better implementation – Jeroen Heier Apr 11 '16 at 04:17
  • Kirill, the goal of my syntax is to start the external program (which prints an order) and continue the process, without waiting for printing to finish. Therefore, the term "fire-and-forget" does seem to apply to what I want. Do I understand that "waitForExit" would wait for printing to finish, before process continues? – Hidalgo Apr 11 '16 at 13:03
  • Jeroen, Thank you for the link. I have read it but still not see how to make it a "better implementation." – Hidalgo Apr 11 '16 at 13:04
  • What is the error you're getting? – svick Apr 11 '16 at 16:39
  • @svick I am not getting an error. Initially I was getting the error pointing to the fact that I missed the key word "static". – Hidalgo Apr 11 '16 at 17:02

2 Answers2

2

Based on the code that you have shared here you are starting a process. This is concerning as you are not actually waiting for the result of the process, in fact -- it is fire and forget regardless of the async and await keywords unless you wait for the process to exit. There are several ways to do this:

public static Task WaitForExitAsync(this Process process, 
    int milliseconds,
    CancellationToken cancellationToken = default(CancellationToken))
{    
    return Task.Run(() => process.WaitForExit(milliseconds), cancellationToken);
}

For example, here is an extension method you could use to wrap the waiting for the process in a Task. It could then be awaited like this:

public async Task PrintOrderAsync(string PrintExe, string ExePath, int nOrderNo)
{
    return Process.Start(ExePath + PrintExe, nOrderNo.ToString())
                  .WaitForExitAsync(5000);
}

// Then you could await it wherever...
await PrintOrderAsync(PrintExe, ExePath, nOrderNo);

Alternatively, if you do not want to wait for it to complete (i.e.; you want fire and forget, like you have now) do this:

Process.Start(ExePath + PrintExe, nOrderNo.ToString())

Do not wrap it in a Task or anything, it is an entirely separate process anyways (personally, I prefer the first option I shared).

David Pine
  • 23,787
  • 10
  • 79
  • 107
  • David, First thank you for the post and code. Something confuses me (and it is me, of course). If I don't use async and await at all (as you pointed) and simply start the process with Process.Start( ExePath + PrintExt, nOrderNo.ToString()), wouldn't this process start on the same thread as the "main" code and wouldn't the "main" code have to wait for process to finish? That is, I thought that the reason to use async and await is to "move" the Process to a new thread and therefore "free" the "main" thread to continue. False logic? :) – Hidalgo Apr 11 '16 at 19:10
  • 1
    Not at all, it starts a new process that is entirely disconnected from your code that started it. Try this, write a simple console application that does `Process.Start("notepad")`. And immediately exit the console app -- notepad remains open and is started in its own process -- in fact these two processes cannot share threads at all. – David Pine Apr 11 '16 at 19:18
  • David, thank you very much. You are right. Hopefully the entire exercise of learning how to use async and await has not been a complete waste of time and I will get to use it at some point. – Hidalgo Apr 11 '16 at 19:33
1

Try

string result = await PrintOrderAsync(PrintExe, ExePath, nOrderNo);
M. Tovbin
  • 537
  • 3
  • 12
  • M Tovbin, I admit that I know almost nothing about the topic I brought up here. Therefore, I don't know what would be the difference in using your syntax vs. the await Task.Factory.StartNew(() => Process.Start But thank you for your suggestion. – Hidalgo Apr 11 '16 at 13:08
  • 1
    when you do an await Task.Factory.StartNew it spins up a new thread and executes your Process.Start on that thread. When it spins up a new thread, the current thread is freed. This is fine if that is what you are attempting to do. Since your PrintOrderAsync method returns a Task of string, you have to await the PrintOrderAsync when making a call to it and assign the result to a string variable. – M. Tovbin Apr 11 '16 at 13:14
  • M Tovbin, thank you very much for the explanation. But when I change my code "Task result = PrintOrderAsync(PrintExe, ExePath, nOrderNo);" with your code "string result = await PrintOrderAsync(PrintExe, ExePath, nOrderNo);" the VS shows the following error: "The await operator can only be used within an async method. Consider making this method with 'async' modifier and changing its return type to 'Task'" – Hidalgo Apr 11 '16 at 13:43
  • 1
    When you use an await inside a method, that method must be marked as async. If it's not a delegate, it should also return a Task and should be awaited all the way up the chain. When using async/await, you need to use it all the way up the chain. If the method is not a delegate and you leave it as async void, your error handling will not work as you would expect it. – M. Tovbin Apr 11 '16 at 13:48