I am in a scenario wherein I need to call an exe process via an API endpoint (fire and forget). However, the exe that I will call is a long running one but I need to wait for it to finish to execute further codes. This will happen in the background and the client shouldn't really wait for it. When I'm trying the code below, the call is waiting for the exe process to finish. I've also tried making the method "async" but still same result.
[HttpPost]
public async Task<bool> ToggleCarWeaverService(string command)
{
try
{
var fileName = #"SomeExe.exe";
await _service.RunProcessAsync(command, fileName);
return true;
}
catch (Exception ex)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
}
public Task<int> RunProcessAsync(string command, string fileName)
{
// Use ProcessStartInfo class
ProcessStartInfo startInfo = new ProcessStartInfo
{
CreateNoWindow = false,
UseShellExecute = true,
FileName = fileName,
WindowStyle = ProcessWindowStyle.Normal,
Arguments = command
};
try
{
var tcs = new TaskCompletionSource<int>();
var process = new Process
{
StartInfo = startInfo,
EnableRaisingEvents = true
};
process.Exited += (sender, args) =>
{
tcs.SetResult(process.ExitCode);
//Do more code
process.Dispose();
};
process.Start();
return tcs.Task;
}
catch (Exception ex)
{
// Log error.
throw ex;
}
}
Do not await the async task.
Use Task.Run to wrap and continue
[HttpPost]
public IHttpActionResult ToggleCarWeaverService(string command) {
Task.Run(async () => {
var fileName = #"SomeExe.exe";
await _service.RunProcessAsync(command, fileName);
//...other code after process finish
});
return Ok();
}
The answers given here will, correctly, answer your question - that is, by not awaiting the result, your call will return immediately. However, there is no guarantee that the process will continue to run. Once you've left the function you, effectively, have a rouge process that may or may not complete.
Most Cloud providers have a solution to this (e.g. Azure WebJobs), or you could roll your own using something like HangFire.
Then you shouldn't await the call further at all and thus change the line where you await the call RunProcessAsync to
_service.RunProcessAsync(command, fileName);
Related
I have this task in C# that should return the standard output of DISM, so I can use it where i need:
public async Task<StreamReader> DISM(string Args)
{
StreamReader DISMstdout = null;
await Task.Run(() =>
{
Process DISMcmd = new Process();
if (Environment.Is64BitOperatingSystem)
{
DISMcmd.StartInfo.FileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "SysWOW64", "dism.exe");
}
else
{
DISMcmd.StartInfo.FileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "System32", "dism.exe");
}
DISMcmd.StartInfo.Verb = "runas";
DISMcmd.StartInfo.Arguments = DISMArguments;
DISMcmd.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
DISMcmd.StartInfo.CreateNoWindow = true;
DISMcmd.StartInfo.UseShellExecute = false;
DISMcmd.StartInfo.RedirectStandardOutput = true;
DISMcmd.EnableRaisingEvents = true;
DISMcmd.Start();
DISMstdout = DISMcmd.StandardOutput;
DISMcmd.WaitForExit();
});
return DISMstdout;
}
But it doesn't really work.
If I want to read the standardoutput from another task I can't (because it is empty) So there must be a problem with my task?.
public async Task Test()
{
await Task.Run(() =>
{
StreamReader DISM = await new DISM("/Get-ImageInfo /ImageFile:" + ImagePath + #" /Index:1");
string data = string.Empty;
MessageBox.Show(DISM.ReadToEnd()); // this should display a msgbox with the standardoutput of dism
while ((data = DISM.ReadLine()) != null)
{
if (data.Contains("Version : "))
{
// do something
}
}
});
}
What is wrong with this piece of code?
The way I'd write your method to exploit async..await as opposed to the legacy asynchronous approaches is like this:
public async Task<TResult> WithDism<TResult>(string args, Func<StreamReader, Task<TResult>> func)
{
return await Task.Run(async () =>
{
var proc = new Process();
var windowsDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
var systemDir = Environment.Is64BitOperatingSystem ? "SysWOW64" : "System32";
proc.StartInfo.FileName = Path.Combine(windowsDir, systemDir, "dism.exe");
proc.StartInfo.Verb = "runas";
proc.StartInfo.Arguments = args;
proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.Start();
Console.Error.WriteLine("dism started");
var result = await func(proc.StandardOutput);
Console.Error.WriteLine("func finished");
// discard rest of stdout
await proc.StandardOutput.ReadToEndAsync();
proc.WaitForExit();
return result;
});
}
Since realistically, the only part where significant blocking can occur when spawning a process is as you handle the output it produces. Used like this:
var task = WithDism("/?", async sr => await sr.ReadToEndAsync()); // or process line-by-line
Console.WriteLine("dism task running");
Console.WriteLine(await task);
it produces the following output
dism task running
dism started
func finished
Error: 740
Elevated permissions are required to run DISM.
Use an elevated command prompt to complete these tasks.
Do note that when using subprocesses, it's your job to make sure they correctly exit or are shut down to avoid leaving zombie processes around. That's why I've added the possibly redundant ReadToEndAsync() - in case func still leaves some output unconsumed, this should allow the process to reach its natural end.
However, this means the calling function will only proceed once that happens. If you leave behind a lot of unconsumed output you're not interested in, this will cause an unwanted delay. You could work around this by spawning off this cleanup to a different background task and returning the result immediately using something like:
Task.Run(() => {
// discard rest of stdout and clean up process:
await proc.StandardOutput.ReadToEndAsync();
proc.WaitForExit();
});
but I admit I'm going a bit out on a limb there, I'm not entirely sure about the robustness of just letting a task "run wild" like that. What the appropriate way to clean up the process is will, of course, depend on what it's actually doing after you get the output you want to return from func.
I'm using synchronous calls to Console there because they only serve to illustrate the timing of events, I want to know that as execution reaches that point. Normally you would use async in a "viral" way to make sure control passes back to top-level as soon as possible.
After playing around with this using Benchmark.NET, it seems that starting a process (I tried DISM and Atom to have something hefty) - from setup to Start() - takes about 50 milliseconds. This seems pretty negligible to me for this use. After all, 50ms is good enough latency for say playing League of Legends, and you're not going to start these in a tight loop.
I'd like to provide an alternative answer of "don't bother with Task.Run() and just use async I/O in a straightforward way" unless you absolutely need to get rid of that delay and believe spawning off a background thread will help:
static string GetDismPath()
{
var windowsDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
var systemDir = Environment.Is64BitOperatingSystem ? "SysWOW64" : "System32";
var dismExePath = Path.Combine(windowsDir, systemDir, "dism.exe");
return dismExePath;
}
static Process StartDism(string args)
{
var proc = new Process
{
StartInfo =
{
FileName = GetDismPath(),
Verb = "runas",
Arguments = args,
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true
}
};
proc.Start();
return proc;
}
static void Cleanup(Process proc)
{
Task.Run(async () =>
{
proc.StandardInput.Close();
var buf = new char[0x1000];
while (await proc.StandardOutput.ReadBlockAsync(buf, 0, buf.Length).ConfigureAwait(false) != 0) { }
while (await proc.StandardError.ReadBlockAsync(buf, 0, buf.Length).ConfigureAwait(false) != 0) { }
if (!proc.WaitForExit(5000))
{
proc.Kill();
}
proc.Dispose();
});
}
static async Task Main(string[] args)
{
var dismProc = StartDism("/?");
// do what you want with the output
var dismOutput = await dismProc.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
await Console.Out.WriteAsync(dismOutput).ConfigureAwait(false);
Cleanup(dismProc);
}
I'm only using Task.Run() to keep the cleanup off the main thread in case you need to do something else while DISM keeps producing output you're not interested in that you do not wish to kill outright.
I found beneath code for execute some process without freezing UI. This code is executed when 'Start Work' button is pressed. And I think users would stop this work by 'Stop' button. So I found this article at MSDN.. https://msdn.microsoft.com/en-us/library/jj155759.aspx . But, It was hard that applying this CancellationToken at this code.. Anyone can help this problem?
I use public static async Task<int> RunProcessAsync(string fileName, string args) method only.
Code (From https://stackoverflow.com/a/31492250):
public static async Task<int> RunProcessAsync(string fileName, string args)
{
using (var process = new Process
{
StartInfo =
{
FileName = fileName, Arguments = args,
UseShellExecute = false, CreateNoWindow = true,
RedirectStandardOutput = true, RedirectStandardError = true
},
EnableRaisingEvents = true
})
{
return await RunProcessAsync(process).ConfigureAwait(false);
}
}
// This method is used only for internal function call.
private static Task<int> RunProcessAsync(Process process)
{
var tcs = new TaskCompletionSource<int>();
process.Exited += (s, ea) => tcs.SetResult(process.ExitCode);
process.OutputDataReceived += (s, ea) => Console.WriteLine(ea.Data);
process.ErrorDataReceived += (s, ea) => Console.WriteLine("ERR: " + ea.Data);
bool started = process.Start();
if (!started)
{
//you may allow for the process to be re-used (started = false)
//but I'm not sure about the guarantees of the Exited event in such a case
throw new InvalidOperationException("Could not start process: " + process);
}
process.BeginOutputReadLine();
process.BeginErrorReadLine();
return tcs.Task;
}
Usage :
var cancelToken = new CancellationTokenSource();
int returnCode = async RunProcessAsync("python.exe", "foo.py", cancelToken.Token);
if (cancelToken.IsCancellationRequested) { /* something */ }
When the start button clicked, it starts some python script.
When script is running and user wants to stop it, user presses stop button.
Then program executes below code.
cancelToken.Cancel();
Thank you very much for reading this question.
The simple answer is that you can just call process.Kill() when the token is canceled:
cancellationToken.Register(() => process.Kill());
But there are two problems with this:
If you attempt to kill a process that doesn't exist yet or that has already terminated, you get an InvalidOperationException.
If you don't Dispose() the CancellationTokenRegistration returned from Register(), and the CancellationTokenSource is long-lived, you have a memory leak, since the registrations will stay in memory as long as the CancellationTokenSource.
Depending on your requirements, and your desire for clean code (even at the cost of complexity) it may be okay to ignore problem #2 and work around problem #1 by swallowing the exception in a catch.
It's quite simple now:
process.WaitForExitAsync(token);
I can't seem to read a file from a background task in a windows store app. Here's the code that reads the file content:
async private static Task<string> ReadAsync(string FileName)
{
var folder = ApplicationData.Current.LocalFolder;
var file = await folder.GetFileAsync(FileName);
Windows.Storage.Streams.IRandomAccessStreamWithContentType inputStream = null;
try
{
inputStream = await file.OpenReadAsync();
}
catch (Exception ex)
{
throw (ex);
}
string content = string.Empty;
using (Stream stream = inputStream.AsStreamForRead())
{
using (StreamReader reader = new StreamReader(stream))
{
try
{
// *** program exits on this line
content = await Task.Run(() => reader.ReadToEnd());
}
catch(Exception ex)
{
// no error is caught
content = ex.Message;
}
}
}
return content;
}
The program exits on the line that calls ReadToEnd() on the StreamReader - no error is caught in the try catch block. In the output window I get:
The program '[8968] backgroundTaskHost.exe: Managed (v4.0.30319)' has exited with code 1 (0x1)
Is it possible to access files a background task? If so, where am I going wrong?
It would be helpful if you posted your IBackgroundTask code. Without seeing it I suspect you didn't call GetDeferral() inside it, e.g.:
public async void Run(IBackgroundTaskInstance taskInstance)
{
var deferral = taskInstance.GetDeferral();
var contents = await ReadAsync("MyFile.txt");
deferral.Complete();
}
You need to call GetDeferral() whenever you are making asynchronous calls inside your background task. This way you tell the runtime it needs to wait for the asynchronous call to complete and not stop the background task as soon as Run exits.
Once you're done, i.e. usually at the end of your Run method, you need to call Complete() on the deferral instance to notify the runtime that you're done.
There are already system classes (DataReader) to read file asynchronously, so I'm not sure why you decided to write your own.
Apparently, I'm not understanding how to use the ContinueWith method. My goal is to execute a task, and when complete, return a message.
Here's my code:
public string UploadFile()
{
if (Request.Content.IsMimeMultipartContent())
{
//Save file
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);
string filename = "Not set";
task.ContinueWith(o =>
{
//File name
filename = provider.BodyPartFileNames.First().Value;
}, TaskScheduler.FromCurrentSynchronizationContext());
return filename;
}
else
{
return "Invalid.";
}
}
The variable "filename" always returns "Not set". It seems the code within the ContinueWith method is never called. (It does get called if I debug through it line by line in VS.)
This method is being called in my ASP.NET Web API controller / Ajax POST.
What am I doing wrong here?
If you're using an asynchronous operation, the best approach would be to make your operation asynchronous as well, otherwise you'll lose on the advantages of the async call you're making. Try rewriting your method as follows:
public Task<string> UploadFile()
{
if (Request.Content.IsMimeMultipartContent())
{
//Save file
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);
return task.ContinueWith<string>(contents =>
{
return provider.BodyPartFileNames.First().Value;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
else
{
// For returning non-async stuff, use a TaskCompletionSource to avoid thread switches
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
tcs.SetResult("Invalid.");
return tcs.Task;
}
}
The reasons for your variable not being set are:
the tasks are instantiated, but not run.
even if the tasks ran, the function would probably return before they finished running so, it would still return "Not set". The fix for this is waiting for the final task (the one setting fileName) to finish.
Your code could be fixed like this:
public string UploadFile()
{
if (Request.Content.IsMimeMultipartContent())
{
//Save file
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
Task<IEnumerable<HttpContent>> task = Request.Content.ReadAsMultipartAsync(provider);
string filename = "Not set";
var finalTask = task.ContinueWith(o =>
{
//File name
filename = provider.BodyPartFileNames.First().Value;
}, TaskScheduler.FromCurrentSynchronizationContext());
task.Start();
finalTask.Wait();
return filename;
}
else
{
return "Invalid.";
}
}
The additions are the following:
assigned the return value of task.ContinueWith to a variable called finalTask. We need this task, because we'll wait for it to finish
started the task (the task.Start(); line)
waited for the final task to finish before returning (finalTask.Wait();)
If possible, please consider not implementing this asynchronously, because in the end it's synchronous (you're waiting for it to finish) and the current implementation adds complexity that could probably be avoided.
Consider doing something along these lines (if possible):
public string UploadFile()
{
if (Request.Content.IsMimeMultipartContent())
{
//Save file
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(HttpContext.Current.Server.MapPath("~/Files"));
Request.Content.ReadAsMultipart(provider); // don't know if this is really valid.
return provider.BodyPartFileNames.First().Value;
}
else
{
return "Invalid.";
}
}
Disclaimer: I have not actually executed the above code; I just wrote it to illustrate what should be done.
You should return the type Task<T> from the method, in this case it would be a Task<string>.
You are using an asynch operation. If you want to wait for its completion, you have to use the Wait method otherwise of your task:
task.ContinueWith(o =>
{
//File name
filename = provider.BodyPartFileNames.First().Value;
).Wait();
return filename;
Edit:
Some asynch methods start the task as soon as it is created, whereas other ask you to explicitly start them. You have to consult the documentation for each to be sure. In this case, it appears the task does start automatically.
I want to wait for a process to finish, but Process.WaitForExit() hangs my GUI. Is there an event-based way, or do I need to spawn a thread to block until exit, then delegate the event myself?
As of .NET 4.0/C# 5, it's nicer to represent this using the async pattern.
/// <summary>
/// Waits asynchronously for the process to exit.
/// </summary>
/// <param name="process">The process to wait for cancellation.</param>
/// <param name="cancellationToken">A cancellation token. If invoked, the task will return
/// immediately as canceled.</param>
/// <returns>A Task representing waiting for the process to end.</returns>
public static Task WaitForExitAsync(this Process process,
CancellationToken cancellationToken = default(CancellationToken))
{
if (process.HasExited) return Task.CompletedTask;
var tcs = new TaskCompletionSource<object>();
process.EnableRaisingEvents = true;
process.Exited += (sender, args) => tcs.TrySetResult(null);
if(cancellationToken != default(CancellationToken))
cancellationToken.Register(() => tcs.SetCanceled());
return process.HasExited ? Task.CompletedTask : tcs.Task;
}
Usage:
public async void Test()
{
var process = new Process("processName");
process.Start();
await process.WaitForExitAsync();
//Do some fun stuff here...
}
process.EnableRaisingEvents = true;
process.Exited += [EventHandler]
UPDATE: .NET 5 now includes Process.WaitForExitAsync() natively, you can find the implementation here. It's very similar to the below extension method.
Previous Answer:
Here's an extension method that's slightly cleaner, because it cleans up the cancellation token registration and Exited event. It also handles the race condition edge cases, where the process could end after it started, but before the Exited event was attached. It uses the new local functions syntax in C# 7. The return value is the process return code.
public static class ProcessExtensions
{
public static async Task<int> WaitForExitAsync(this Process process, CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
void Process_Exited(object sender, EventArgs e)
{
tcs.TrySetResult(process.ExitCode);
}
try
{
process.EnableRaisingEvents = true;
}
catch (InvalidOperationException) when (process.HasExited)
{
// This is expected when trying to enable events after the process has already exited.
// Simply ignore this case.
// Allow the exception to bubble in all other cases.
}
using (cancellationToken.Register(() => tcs.TrySetCanceled()))
{
process.Exited += Process_Exited;
try
{
if (process.HasExited)
{
tcs.TrySetResult(process.ExitCode);
}
return await tcs.Task.ConfigureAwait(false);
}
finally
{
process.Exited -= Process_Exited;
}
}
}
}
If you choose #MgSam answer, be aware, if you pass through WaitForExitAsync some CancellationToken, that will be automatically canceled after the specified delay, you can get an InvalidOperationException. To fix that, you need to change
cancellationToken.Register(tcs.SetCanceled);
to
cancellationToken.Register( () => { tcs.TrySetCanceled(); } );
P.S.: don't forget to dispose your CancellationTokenSource in time.
As of .NET5 there is now a WaitForExitAsync provided by the Process class.
await process.WaitForExitAsync( token );
According to this link the WaitForExit() method is used to make the current thread wait until the associated process terminates. However, the Process does have an Exited event that you can hook into.
Ryan solution works good on windows. On OSX strange things happened it could be a Deadlock at tcs.TrySetResult()! There are 2 solutions:
First one:
Wrap tcs.TrySetResult() to a Task.Run():
public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource<bool>();
void Process_Exited(object sender, EventArgs e)
{
Task.Run(() => tcs.TrySetResult(true));
}
process.EnableRaisingEvents = true;
process.Exited += Process_Exited;
try
{
if (process.HasExited)
{
return;
}
using (cancellationToken.Register(() => Task.Run(() => tcs.TrySetCanceled())))
{
await tcs.Task;
}
}
finally
{
process.Exited -= Process_Exited;
}
}
Conversation about this and more details:
Calling TaskCompletionSource.SetResult in a non blocking manner
Second one:
public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken)
{
while (!process.HasExited)
{
await Task.Delay(100, cancellationToken);
}
}
You can increase the Polling interval from 100 ms to more depending your application.
Fast and simple solution:
async Task RunProcessWait(string Path)
{
Process MyProcess = Process.Start(Path);
while (!MyProcess.HasExited)
await Task.Delay(100);
}
await RunProcessWait("C:\...")
Use System.Diagnostics.Process.Exited