I have asked this question the other day, but neither I had an answer nor could I made it work. So I tried to slim it down as it was a lot of noise in the question.
Thing is, if I expose in a web api a all to a method that runs cmd.exe it works fine if I don't call it two times per request.
I mean, this code works fine:
public class FilesController : ApiController
{
private readonly IRunner _runner;
public FilesController(IRunner runner)
{
_runner = runner;
}
public string Get()
{
return _runner.GetFiles();
}
}
public class Runner : IRunner
{
public Runner()
{
//var cd = #"cd C:\DummyFolder";
//RunCmdPromptCommand(cd);
}
public string GetFiles()
{
var dir = #"cd C:\DummyFolder & dir";
//var dir = "dir";
return RunCmdPromptCommand(dir);
}
private string RunCmdPromptCommand(string command)
{
var process = new Process
{
StartInfo =
{
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
RedirectStandardError = true,
RedirectStandardOutput = true,
FileName = #"cmd.exe",
Arguments = string.Format("/C {0}", command)
}
};
process.Start();
var error = process.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(error))
{
throw new Exception(error);
}
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return output;
}
}
But if I uncomment the lines commented (and obviously comment out the first line of GetFiles, when the code reaches for the second time (i.e. with "dir") the RunCmdPromptCommand it gets stuck in the line where it tries to read the standard error.
I don't know why, and I don't know how to force the exit whenever it could happen (might be other scenarios that can happen)
Thanks,
This is because the:
process.StandardOutput.ReadToEnd();
Is a synchronous operation.
Excerpt from MSDN:
The redirected StandardError stream can be read synchronously or
asynchronously. Methods such as Read, ReadLine, and ReadToEnd perform
synchronous read operations on the error output stream of the process.
These synchronous read operations do not complete until the associated
Process writes to its StandardError stream, or closes the stream.
In other words, as long as the process doesn't write any standard error or closes the stream, it will get stuck there forever.
To fix this, I recommend to use Async BeginErrorReadLine. Excerpt from MSDN:
In contrast, BeginErrorReadLine starts asynchronous read operations on
the StandardError stream. This method enables a designated event
handler for the stream output and immediately returns to the caller,
which can perform other work while the stream output is directed to the event handler.
Which I think will be suitable for your need.
To use that. the example given in the MSDN is pretty straightforward. Check out especially these lines:
netProcess.ErrorDataReceived += new DataReceivedEventHandler(NetErrorDataHandler); //note this event handler add
if (errorRedirect) //in your case, it is not needed
{
// Start the asynchronous read of the standard
// error stream.
netProcess.BeginErrorReadLine(); //note this
}
And how to define the event handler:
private static void NetErrorDataHandler(object sendingProcess,
DataReceivedEventArgs errLine)
{
// Write the error text to the file if there is something
// to write and an error file has been specified.
if (!String.IsNullOrEmpty(errLine.Data))
{
if (!errorsWritten)
{
if (streamError == null)
{
// Open the file.
try
{
streamError = new StreamWriter(netErrorFile, true);
}
catch (Exception e)
{
Console.WriteLine("Could not open error file!");
Console.WriteLine(e.Message.ToString());
}
}
if (streamError != null)
{
// Write a header to the file if this is the first
// call to the error output handler.
streamError.WriteLine();
streamError.WriteLine(DateTime.Now.ToString());
streamError.WriteLine("Net View error output:");
}
errorsWritten = true;
}
if (streamError != null)
{
// Write redirected errors to the file.
streamError.WriteLine(errLine.Data);
streamError.Flush();
}
}
}
Related
I have a C# program that wants to interact with an external process written in C++. I believe this C++ process is using correct standard input. I just can't seem to get my C# code to not hang when trying to write to Process.StandardInput.
I've seen countless examples using Process.StandardInput.Close() when done writing. Every StackOverflow answer I found says to use this, and it does work. The problem is I can't close the StreamWriter because I'm not done interacting with the process. The process is a state machine that holds variables created using stdin, parses expressions, and returns an evaluation. I am expected to keep giving the process input after each output.
Does anyone have an example where Process.StandardInput.WriteLine is used more than once without closing or restarting the process?
This is how the C++ process is reading input. This example simply echos back the input and waits for another.
int main () {
std::string input;
while (getline(std::cin, input)) {
std::cout << input << std::endl;
}
}
My C# program tries to interact with this process using this wrapper class.
public class Expression {
System.Diagnostics.Process p;
public Expression () {
p = new System.Diagnostics.Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.FileName = "InputEcho.exe";
p.Start();
p.StandardInput.AutoFlush = true;
}
public void Run (in string input, out string output) {
p.StandardInput.WriteLine(input);
// p.StandardInput.Close();
output = p.StandardOutput.ReadToEnd();
}
}
This works when I uncomment p.StandardInput.Close() but then subsequent calls to Expression.Run() won't work because the writer is closed.
Main program
Expression expn = new();
string output;
Console.WriteLine("Expression start");
expn.Run("Hello", output);
Console.WriteLine(output);
expn.Run("Hi", output);
Console.WriteLine(output);
Expected output
Expression start
Hello
Hi
Actual output
Expression start
EDIT:
#Matthew Andrews provided a really good answer that works, but it's not quite what I'm after. I didn't think about using event delegates to receive output data, and I see why: It's hard to implement this into the wrapper that I want to use to build a process-relevant API. What I mean by this is that I want to write some method that communicates with the process, give it input, receive the output, and return this data to the caller before doing anything else. My Expression.Run method exemplifies this perfectly.
Here's an example of what the root caller would look like in a greater C# program.
bool GetConditionEval (string condition, SomeDataType data) {
// Makes another call to 'Run' that commands the C++ process to store a variable
// Input looks like this: "variableName = true" (aka key/value pairs)
Expression.SetVar(data.name, "true");
// Don't ask why I'm using an external process to set variables using string expressions.
// It's a company proprietary thing.
string output;
Expression.Run(in condition, out output);
if (output.ToLower() == "true") return true;
else if (output.ToLower() == "false") return false;
else throw new Exception("Output is something other than true or false.");
}
This is why I'd like for Run to immediately return the output it receives from the process.
If not, I guess I could find a way for a delegate method to store the output in a global container and the GetConditionEval can just reach into that. I worry about race conditions though.
Side note:
Since I do expect the API that is contained in this C++ process to eventaully take other forms, spinning this up as a standalone process and invoking the API via stdin is really a stopgap for now so I don't have to convert thousands of lines of C++ code into C#.
SOLUTION:
I figured out a solution using the asynchronous method Matthew suggested while having a linear process of sending input and working immediately off the output in the same sequence. I reconfigured my wrapper class to queue each output received from the event listener. This sets up a pattern where I can call one method to send input, and then call another method right after to pop output data off the queue if any. I compensated for the fact that output data might not be avaliable immediately by simply waiting if the queue is empty and then moving forward once something is there. This unfortuately makes it a blocking call if it does have to wait, but it's the best I have so far. I also implemented a failsafe so it doesn't wait indefinately.
public class Expression {
System.Diagnostics.Process p = new();
System.Collections.Generic.Queue<string> outputQ = new();
public Expression () {
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.FileName = "C2E2.exe";
p.OutputDataReceived += (s, e) => {
outputQ.Enqueue(e.Data);
};
p.Start();
p.BeginOutputReadLine();
}
/// Returns custom exception object if error is encountered.
public GRLib.Exception Run (in string input) {
if (p == null) return GRLib.Exception.New("Expression Evaluator not operational.");
try {
p.StandardInput.WriteLine(input);
}
catch (Exception e) {
return GRLib.Exception.New(e.Message);
}
return null;
}
/// Returns error code 1 if timeout occured.
/// Timeout is represented in milliseconds.
/// Blocking call.
public GRLib.Exception GetOutput (out string output, int timeout = 2000) {
/// Wait for something to show in the queue.
/// Waits indefinitely if timeout is 0.
/// If anyone knows a better way to implement this waiting loop,
/// please let me know!
int timeWaited = 0;
while (outputQ.Count == 0) {
System.Threading.Thread.Sleep(100);
if (timeout != 0 && (timeWaited += 100) > timeout) {
output = "ERR";
return GRLib.Exception.New(1, "Get timed out.");
}
}
output = outputQ.Dequeue();
return null;
}
...
}
Example usage
Expression expression = new();
var e = expression.Run("3 > 2");
if (e != null) // Handle error
string output;
e = expression.GetOutput(out output);
if (e != null) // Handle error
// 'output' should now be 'true' which can then be used in other parts of this program.
While the event listener in a standalone fashion works great, I need the output from the process to be returned in the same stack where the input is given because this is going to be part of a more complex call graph.
The problem you're observing is due to the synchronous nature of Process.StandardOutput.ReadToEnd(). Instead, you should listen for your output asynchronously by setting Process.BeginOutputReadLine() and utilizing the Process.OutputDataReceived event.
Here is a quick example to get you started:
var p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = #"ConsoleApplication1.exe";
p.OutputDataReceived += (s, e) =>
{
Console.WriteLine(e.Data);
};
p.Start();
p.BeginOutputReadLine();
while (true)
{
var readLine = Console.ReadLine();
p.StandardInput.WriteLine(readLine);
}
And here is the c++ I used for ConsoleApplication1.exe:
int main()
{
std::cout << "Hello World!\n";
std::string input;
while (std::getline(std::cin, input)) {
std::cout << input << std::endl;
}
}
Running my example will print Hello World! and then proceed to parrot whatever else you enter into the console.
I'm using powershell in C# with system.management.automation and I can access both the output and the error stream sucessfully. For most applications this is great but i'm right now in a situation where I need to get the output of a powershell command while it is running in c# and i'm lost.
I've tried subscribing to outputcollection.DataAdded, i've tried subscribing to the powershell instance verbose stream, but neither of them are getting called when powershell gives an output.
Here's the code I have so far
public async Task<string> CMD(string script)
{
ps = PowerShell.Create();
string errorMsg = "";
string output;
ps.AddScript(script);
ps.AddCommand("Out-String");
PSDataCollection<PSObject> outputCollection = new();
ps.Streams.Error.DataAdded += (object sender, DataAddedEventArgs e) =>
{ errorMsg = ((PSDataCollection<ErrorRecord>)sender)[e.Index].ToString(); };
IAsyncResult result = ps.BeginInvoke<PSObject, PSObject>(null, outputCollection);
while (!result.IsCompleted)
{
await Task.Delay(100);
}
StringBuilder stringBuilder = new();
foreach (PSObject outputItem in outputCollection)
{
stringBuilder.AppendLine(outputItem.BaseObject.ToString());
}
output = stringBuilder.ToString();
//Clears commands added to runspace
ps.Commands.Clear();
Debug.WriteLine(output);
if (!string.IsNullOrEmpty(errorMsg))
MessageBox.Show(errorMsg, "Error");
return output.Trim();
}
I've also tried checking the outputcollection in the while loop but it doesn't give me the output until the command is done.
The command i'm trying to use is Connect-ExchangeOnline -Device
To simulate it in C# it would work the same as doing sleep 5;echo test;sleep 5
where I then want the program to display test after 5 seconds not after the full 10 seconds.
EDIT:
When using "Connect-ExchangeOnline -Device" powershell will deliver this output and wait for the user to complete said task. The issue being that I can't display this in C# because my C# code waits for the powershell command to be finished. And outputcollection.DataAdded never seems to be called.
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code CDWS27A56 to authenticate.
Unfortunately, Connect-ExchangeOnline is meant only for interactive console use, and specifically gets around allowing the output to be captured (Possibly by writing directly to the $host window it was called from?).
Normally, you could try using Tee-Object or Start-Transcript/Stop-Transcript with redirection to dump all output:
Connect-ExchangeOnline -Device *>&1 | Tee-Object -FilePath C:\temp\tee.txt -Append
# Then from another process:
Get-Content C:\temp\tee.txt
Or try starting a powershell Job, which keeps all of its output in the job object's properties:
$job = Start-ThreadJob -Name Connecting -ScriptBlock { Connect-ExchangeOnline -Device }
# Wait for prompt...
$job.Output
$job.Information
However, neither of these actually grab the device authentication code.
Currently to use -Device, you need to have a visible powershell window and have the user complete their device authentication there.
You can always use one of the other authentication types:
Connect-ExchangeOnline -UserPrincipalName username#domain.tld
This version will automatically launch a Modern Authentication prompt or a browser page. Depending on your use case, it is effectively the same.
I would pipe the script outputs to a file, then have your c# code read that file, and filter out the code.
Alternatively you could use a class that exposes 2 StringBuilders as properties through which you can use to get the script output and filter out the code:
using System.Diagnostics;
using System.Text;
public sealed class ProcessOptions
{
public bool WaitForProcessExit { get; set; }
public bool Executing { get; internal set; } = true;
}
public class InvokePowershell
{
public static StringBuilder stdout { get; private set; } = null;
public static StringBuilder stderr { get; private set; } = null;
public void string Start(string script)
{
var process = new Process();
var options = new ProcessOptions()
{
WaitForProcessExit = true,
};
process.StartInfo.FileName = "powershell"; // (or pwsh for powershell core).
process.StartInfo.Arguments = script;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = true; // true does not always work so use caution with this.
process.StartInfo.CreateNoWindow = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.WorkingDirectory = Directory.GetCurrentDirectory();
process.Shell(options)
}
private static void Shell(this Process process, ProcessOptions options)
{
if (stdout is not null && stderr is not null)
{
stdout = null;
stderr = null;
}
process.OutputDataReceived += (_, e) =>
{
if (e.Data is null)
{
return;
}
if (stdout is null)
{
stdout = new StringBuilder();
}
else
{
stdout.AppendLine();
}
stdout.Append(e.Data);
};
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is null)
{
return;
}
if (stderr is null)
{
stderr = new StringBuilder();
}
else
{
stderr.AppendLine();
}
stderr.Append(e.Data);
};
process.Start();
options.Executing = false;
if (process.StartInfo.RedirectStandardError)
process.BeginErrorReadLine();
if (process.StartInfo.RedirectStandardOutput)
process.BeginOutputReadLine();
if (options.WaitForProcessExit)
process.WaitForExit();
}
}
And then you could then make a way to Fire and forget that code (so that way it does not get blocked until powershell exits, then simply in the code you can do the following in your normal code:
while (InvokePowershell.stdout is null || InvokePowershell.stdout.Length < /* length you expect it to be*/)
{
// do nothing but by wait by sleeping for a few milliseconds to avoid wasting cpu cycles with this.
}
// strip the code from the stdout property then use it.
I find doing something like this is much more cleaner, plus then it could easily be ported to powershell core by changing powershell on the Start function to use pwsh which is the program name for powershell core and plus then the code would work on all platforms that powershell core supports which are Windows, Mac, and various other linux distributions out there.
Additionally for this to work powershell or even pwsh if powershell core is wanted to be used instead of powershell that the program must be in the PATH environment variable so it can be invoked inside of the terminal directly.
Also with the code above, you could theoretically not Wait for process exit, however I do not know if those events would trigger and populate the StringBuilders then, likewise the process instance would leave scope and be GC'd resulting in the events also getting GC'd and then the StringBuilders never getting assigned to.
As such that is why I recommend calling InvokePowershell.Start(script); as a delegate as a fire-and-forget call. Then doing a loop that checks if null or is smaller than the expected length of the string outputs then sleep for a few cpu clockcycles (each clockcycle is less than a second), and then filtering out the results from there after that while loop ensures that it is populated for the preprocessing that comes after the loop.
Edit: Instead of having to call InvokePowershell.Start(script); in a fire-and-forget, you can replace the call to process.WaitForExit() and the if check for it entirely with the while loop shown above, pass in the length you expect to the method, and to the (Shell method by adding it as a parameter to it and remove the options argument, instantiation, and type entirely), and then after the while loop breaks (to allow time for the event handlers to add what you need to the property's stringbuilders, you can call process.Kill(); to kill powershell or powershell core.
You can use the code below to get the output of a PowerShell command in real time from a C# application.
This uses a PowerShell Pipeline, which allows you to call a notification handler whenever the PowerShell command/script writes output into the Pipeline. I've implemented the solution below as an async enumerable but if you wanted something non-async you can also just use the Pipeline.Output.DataReady handler to trigger some code to read from the pipeline.
https://gist.github.com/OnKey/83cf98e6adafe5a2b4aaf561b138087b
static async Task Main(string[] args)
{
var script = #"
For ($i=0; $i -le 5; $i++) {
$i
Start-Sleep -s 1
}
";
var p = new Program();
await foreach (var item in p.PowerShellAsyncEnumerable(script))
{
Console.WriteLine(item);
}
}
private IAsyncEnumerable<PSObject> PowerShellAsyncEnumerable(string script)
{
var rs = RunspaceFactory.CreateRunspace();
rs.Open();
var pipeline = rs.CreatePipeline();
pipeline.Commands.AddScript(script);
return new PsAsyncEnumerable(pipeline);
}
internal class PsAsyncEnumerable : IAsyncEnumerable<PSObject>
{
private readonly Pipeline pipe;
public PsAsyncEnumerable(Pipeline pipe) => this.pipe = pipe;
public IAsyncEnumerator<PSObject> GetAsyncEnumerator(CancellationToken cancellationToken = new())
=> new PsAsyncEnumerator(this.pipe);
}
internal class PsAsyncEnumerator : IAsyncEnumerator<PSObject>
{
private readonly Pipeline pipe;
private TaskCompletionSource dataReady = new();
public PsAsyncEnumerator(Pipeline pipe)
{
this.pipe = pipe;
this.pipe.Output.DataReady += NotificationHandler;
this.pipe.Error.DataReady += NotificationHandler;
this.pipe.InvokeAsync();
}
private void NotificationHandler(object sender, EventArgs e)
{
this.dataReady.SetResult();
}
public ValueTask DisposeAsync()
{
this.pipe.Dispose();
return ValueTask.CompletedTask;
}
public async ValueTask<bool> MoveNextAsync()
{
while (!this.pipe.Output.EndOfPipeline)
{
var item = this.pipe.Output.NonBlockingRead(1).FirstOrDefault();
if (item != null)
{
this.Current = item;
return true;
}
await this.dataReady.Task;
this.dataReady = new TaskCompletionSource();
}
return false;
}
public PSObject Current { get; private set; }
}
1. In C# Start BackgroundWorker bw;
2. In bw.DoWork(...) Start PowerShell.
3. In PowerShell write to a File and close it.
4. In the Main thread of C# read the File.
===
using System.ComponentModel;
BackgroundWorker bw = new();
bw.DoWork += Bw_DoWork;
private void Bw_DoWork(object sender, DoWorkEventArgs e)
{
<Start ps>
}
=== in ps
Set obj=CreateObject("Scripting.FileSystemObject")
outFile="C:\File.txt"
Set objFile = obj.CreateTextFile(outFile,True)
objFile.Write "test string"
objFile.Close # it makes file accessible outside ps
=== In the Main thread
<read the C:\File.txt>
===
When a file is created (FileSystemWatcher_Created) in one directory I copy it to another. But When I create a big (>10MB) file it fails to copy the file, because it starts copying already, when the file is not yet finished creating...
This causes Cannot copy the file, because it's used by another process to be raised. ;(
Any help?
class Program
{
static void Main(string[] args)
{
string path = #"D:\levan\FolderListenerTest\ListenedFolder";
FileSystemWatcher listener;
listener = new FileSystemWatcher(path);
listener.Created += new FileSystemEventHandler(listener_Created);
listener.EnableRaisingEvents = true;
while (Console.ReadLine() != "exit") ;
}
public static void listener_Created(object sender, FileSystemEventArgs e)
{
Console.WriteLine
(
"File Created:\n"
+ "ChangeType: " + e.ChangeType
+ "\nName: " + e.Name
+ "\nFullPath: " + e.FullPath
);
File.Copy(e.FullPath, #"D:\levan\FolderListenerTest\CopiedFilesFolder\" + e.Name);
Console.Read();
}
}
There is only workaround for the issue you are facing.
Check whether file id in process before starting the process of copy. You can call the following function until you get the False value.
1st Method, copied directly from this answer:
private bool IsFileLocked(FileInfo file)
{
FileStream stream = null;
try
{
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (IOException)
{
//the file is unavailable because it is:
//still being written to
//or being processed by another thread
//or does not exist (has already been processed)
return true;
}
finally
{
if (stream != null)
stream.Close();
}
//file is not locked
return false;
}
2nd Method:
const int ERROR_SHARING_VIOLATION = 32;
const int ERROR_LOCK_VIOLATION = 33;
private bool IsFileLocked(string file)
{
//check that problem is not in destination file
if (File.Exists(file) == true)
{
FileStream stream = null;
try
{
stream = File.Open(file, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (Exception ex2)
{
//_log.WriteLog(ex2, "Error in checking whether file is locked " + file);
int errorCode = Marshal.GetHRForException(ex2) & ((1 << 16) - 1);
if ((ex2 is IOException) && (errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION))
{
return true;
}
}
finally
{
if (stream != null)
stream.Close();
}
}
return false;
}
From the documentation for FileSystemWatcher:
The OnCreated event is raised as soon as a file is created. If a file
is being copied or transferred into a watched directory, the
OnCreated event will be raised immediately, followed by one or more
OnChanged events.
So, if the copy fails, (catch the exception), add it to a list of files that still need to be moved, and attempt the copy during the OnChanged event. Eventually, it should work.
Something like (incomplete; catch specific exceptions, initialize variables, etc):
public static void listener_Created(object sender, FileSystemEventArgs e)
{
Console.WriteLine
(
"File Created:\n"
+ "ChangeType: " + e.ChangeType
+ "\nName: " + e.Name
+ "\nFullPath: " + e.FullPath
);
try {
File.Copy(e.FullPath, #"D:\levani\FolderListenerTest\CopiedFilesFolder\" + e.Name);
}
catch {
_waitingForClose.Add(e.FullPath);
}
Console.Read();
}
public static void listener_Changed(object sender, FileSystemEventArgs e)
{
if (_waitingForClose.Contains(e.FullPath))
{
try {
File.Copy(...);
_waitingForClose.Remove(e.FullPath);
}
catch {}
}
}
It's an old thread, but I'll add some info for other people.
I experienced a similar issue with a program that writes PDF files, sometimes they take 30 seconds to render.. which is the same period that my watcher_FileCreated class waits before copying the file.
The files were not locked.
In this case I checked the size of the PDF and then waited 2 seconds before comparing the new size, if they were unequal the thread would sleep for 30 seconds and try again.
You're actually in luck - the program writing the file locks it, so you can't open it. If it hadn't locked it, you would have copied a partial file, without having any idea there's a problem.
When you can't access a file, you can assume it's still in use (better yet - try to open it in exclusive mode, and see if someone else is currently opening it, instead of guessing from the failure of File.Copy). If the file is locked, you'll have to copy it at some other time. If it's not locked, you can copy it (there's slight potential for a race condition here).
When is that 'other time'? I don't rememeber when FileSystemWatcher sends multiple events per file - check it out, it might be enough for you to simply ignore the event and wait for another one. If not, you can always set up a time and recheck the file in 5 seconds.
Well you already give the answer yourself; you have to wait for the creation of the file to finish. One way to do this is via checking if the file is still in use. An example of this can be found here: Is there a way to check if a file is in use?
Note that you will have to modify this code for it to work in your situation. You might want to have something like (pseudocode):
public static void listener_Created()
{
while CheckFileInUse()
wait 1000 milliseconds
CopyFile()
}
Obviously you should protect yourself from an infinite while just in case the owner application never releases the lock. Also, it might be worth checking out the other events from FileSystemWatcher you can subscribe to. There might be an event which you can use to circumvent this whole problem.
When the file is writing in binary(byte by byte),create FileStream and above solutions Not working,because file is ready and wrotted in every bytes,so in this Situation you need other workaround like this:
Do this when file created or you want to start processing on file
long fileSize = 0;
currentFile = new FileInfo(path);
while (fileSize < currentFile.Length)//check size is stable or increased
{
fileSize = currentFile.Length;//get current size
System.Threading.Thread.Sleep(500);//wait a moment for processing copy
currentFile.Refresh();//refresh length value
}
//Now file is ready for any process!
So, having glanced quickly through some of these and other similar questions I went on a merry goose chase this afternoon trying to solve a problem with two separate programs using a file as a synchronization (and also file save) method. A bit of an unusual situation, but it definitely highlighted for me the problems with the 'check if the file is locked, then open it if it's not' approach.
The problem is this: the file can become locked between the time that you check it and the time you actually open the file. Its really hard to track down the sporadic Cannot copy the file, because it's used by another process error if you aren't looking for it too.
The basic resolution is to just try to open the file inside a catch block so that if its locked, you can try again. That way there is no elapsed time between the check and the opening, the OS does them at the same time.
The code here uses File.Copy, but it works just as well with any of the static methods of the File class: File.Open, File.ReadAllText, File.WriteAllText, etc.
/// <param name="timeout">how long to keep trying in milliseconds</param>
static void safeCopy(string src, string dst, int timeout)
{
while (timeout > 0)
{
try
{
File.Copy(src, dst);
//don't forget to either return from the function or break out fo the while loop
break;
}
catch (IOException)
{
//you could do the sleep in here, but its probably a good idea to exit the error handler as soon as possible
}
Thread.Sleep(100);
//if its a very long wait this will acumulate very small errors.
//For most things it's probably fine, but if you need precision over a long time span, consider
// using some sort of timer or DateTime.Now as a better alternative
timeout -= 100;
}
}
Another small note on parellelism:
This is a synchronous method, which will block its thread both while waiting and while working on the thread. This is the simplest approach, but if the file remains locked for a long time your program may become unresponsive. Parellelism is too big a topic to go into in depth here, (and the number of ways you could set up asynchronous read/write is kind of preposterous) but here is one way it could be parellelized.
public class FileEx
{
public static async void CopyWaitAsync(string src, string dst, int timeout, Action doWhenDone)
{
while (timeout > 0)
{
try
{
File.Copy(src, dst);
doWhenDone();
break;
}
catch (IOException) { }
await Task.Delay(100);
timeout -= 100;
}
}
public static async Task<string> ReadAllTextWaitAsync(string filePath, int timeout)
{
while (timeout > 0)
{
try {
return File.ReadAllText(filePath);
}
catch (IOException) { }
await Task.Delay(100);
timeout -= 100;
}
return "";
}
public static async void WriteAllTextWaitAsync(string filePath, string contents, int timeout)
{
while (timeout > 0)
{
try
{
File.WriteAllText(filePath, contents);
return;
}
catch (IOException) { }
await Task.Delay(100);
timeout -= 100;
}
}
}
And here is how it could be used:
public static void Main()
{
test_FileEx();
Console.WriteLine("Me First!");
}
public static async void test_FileEx()
{
await Task.Delay(1);
//you can do this, but it gives a compiler warning because it can potentially return immediately without finishing the copy
//As a side note, if the file is not locked this will not return until the copy operation completes. Async functions run synchronously
//until the first 'await'. See the documentation for async: https://msdn.microsoft.com/en-us/library/hh156513.aspx
CopyWaitAsync("file1.txt", "file1.bat", 1000);
//this is the normal way of using this kind of async function. Execution of the following lines will always occur AFTER the copy finishes
await CopyWaitAsync("file1.txt", "file1.readme", 1000);
Console.WriteLine("file1.txt copied to file1.readme");
//The following line doesn't cause a compiler error, but it doesn't make any sense either.
ReadAllTextWaitAsync("file1.readme", 1000);
//To get the return value of the function, you have to use this function with the await keyword
string text = await ReadAllTextWaitAsync("file1.readme", 1000);
Console.WriteLine("file1.readme says: " + text);
}
//Output:
//Me First!
//file1.txt copied to file1.readme
//file1.readme says: Text to be duplicated!
You can use the following code to check if the file can be opened with exclusive access (that is, it is not opened by another application). If the file isn't closed, you could wait a few moments and check again until the file is closed and you can safely copy it.
You should still check if File.Copy fails, because another application may open the file between the moment you check the file and the moment you copy it.
public static bool IsFileClosed(string filename)
{
try
{
using (var inputStream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.None))
{
return true;
}
}
catch (IOException)
{
return false;
}
}
I would like to add an answer here, because this worked for me. I used time delays, while loops, everything I could think of.
I had the Windows Explorer window of the output folder open. I closed it, and everything worked like a charm.
I hope this helps someone.
I have a delegate method to run a heavy process in my app (I must use MS Framework 3.5):
private delegate void delRunJob(string strBox, string strJob);
Execution:
private void run()
{
string strBox = "G4P";
string strJob = "Test";
delRunJob delegateRunJob = new delRunJob(runJobThread);
delegateRunJob.Invoke(strBox, strJob);
}
In some part of the method runJobThread
I call to an external program (SAP - Remote Function Calls) to retrieve data. The execution of that line can take 1-30 mins.
private void runJobThread(string strBox, string strJob)
{
// CODE ...
sapLocFunction.Call(); // When this line is running I cannot cancel the process
// CODE ...
}
I want to allow the user cancel whole process.
How can achieve this? I tried some methods; but I fall in the same point; when this specific line is running I cannot stop the process.
Instead of using the delegate mechanism you have to study the async and await mechanism. When you understand this mechanism you can move to cancellationtoken.
An example doing both things can be found here :
http://blogs.msdn.com/b/dotnet/archive/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis.aspx
Well; I find out a complicated, but effective, way to solve my problem:
a.) I created a "Helper application" to show a notification icon when the process is running (To ensure to don't interfere with the normal execution of the main app):
private void callHelper(bool blnClose = false)
{
if (blnClose)
fw.processKill("SDM Helper");
else
Process.Start(fw.appGetPath + "SDM Helper.exe");
}
b.) I created a Thread that call only the heavy process line.
c.) While the Thread is alive I check for external file named "cancel" (The "Helper application" do that; when the user click an option to cancel the process the Helper create the file).
d.) If exists the file; dispose all objects and break the while cycle.
e.) The method sapLocFunction.Call() will raise an exception but I expect errors.
private void runJobThread(string strBox, string strJob)
{
// CODE ...
Thread thrSapCall = new Thread(() =>
{
try { sapLocFunction.Call(); }
catch { /* Do nothing */ }
});
thrSapCall.Start();
while (thrSapCall.IsAlive)
{
Thread.Sleep(1000);
try
{
if (fw.fileExists(fw.appGetPath + "\\cancel"))
{
sapLocFunction = null;
sapLocTable = null;
sapConn.Logoff();
sapConn = null;
canceled = true;
break;
}
}
finally { /* Do nothing */ }
}
thrSapCall = null;
// CODE ...
}
Works like a charm!
I think you would have to resort to the method described here. Read the post to see why this is a long way from ideal.
Perhaps this might work...
private void runJobThread(string strBox, string strJob, CancellationToken token)
{
Thread t = Thread.CurrentThread;
using (token.Register(t.Abort))
{
// CODE ...
sapLocFunction.Call(); // When this line is running I cannot cancel the process
// CODE ...
}
}
A bit of dnspy exposes a cancel method on nco3.0.
private readonly static Type RfcConnection = typeof(RfcSessionManager).Assembly.GetType("SAP.Middleware.Connector.RfcConnection");
private readonly static Func<RfcDestination, object> GetConnection = typeof(RfcSessionManager).GetMethod("GetConnection", BindingFlags.Static | BindingFlags.NonPublic).CreateDelegate(typeof(Func<RfcDestination, object>)) as Func<RfcDestination, object>;
private readonly static MethodInfo Cancel = RfcConnection.GetMethod("Cancel", BindingFlags.Instance | BindingFlags.NonPublic);
object connection = null;
var completed = true;
using (var task = Task.Run(() => { connection = GetConnection(destination); rfcFunction.Invoke(destination); }))
{
try
{
completed = task.Wait(TimeSpan.FromSeconds(invokeTimeout));
if (!completed)
Cancel.Invoke(connection, null);
task.Wait();
}
catch(AggregateException e)
{
if (e.InnerException is RfcCommunicationCanceledException && !completed)
throw new TimeoutException($"SAP FM {functionName} on {destination} did not respond in {timeout} seconds.");
throw;
}
}
I have 2 applications, A & B.
A calls B within a Process.
B do some stuffs like Console.WriteLine and Console.ReadLine
Thanks to this MSDN Article, I manage somehow to redirect the output of B and to feed its input as well.
What I don't manage to do, is to have the Console.ReadKey function in B work. I made a try catch block arround this function and I got this error message:
Cannot read keys when either application does not have a console, or when console input has been redirected from a file. Try Console.Read
The fact is, I have to use Console.ReadKey, so I need to find a way to make it work...Any idea?
Here are the useful parts of the code of A
In the Main function:
Process P2 = new Process();
P2.StartInfo.FileName = Environment.CurrentDirectory + "\\Main2.exe";
P2.StartInfo.UseShellExecute = false;
P2.StartInfo.RedirectStandardOutput = true;
P2.OutputDataReceived += new DataReceivedEventHandler(WriteOutput);
P2.StartInfo.RedirectStandardInput = true;
P2.Start();
StreamWriter ExeInput = P2.StandardInput;
P2.BeginOutputReadLine();
ConsoleKeyInfo KeyPressed;
do
{
KeyPressed = Console.ReadKey();
if(KeyPressed.Key == ConsoleKey.Enter)
{
Console.WriteLine ();
ExeInput.Write("\n");
}
else
ExeInput.Write(KeyPressed.KeyChar);
} while (!P2.HasExited);
The handler for outputdatareceived:
private static void WriteOutput(object sendingProcess, DataReceivedEventArgs outLine)
{
if (!String.IsNullOrEmpty(outLine.Data))
{
Console.WriteLine(outLine.Data);
}
}
I'm not aware of any way of being able to use ReadKey() when redirecting the StdIn/StdOut of a console program. Furthermore, in order to read and write from the child process, you need to make sure you're using Console.Out.Write()/Console.In.Read() to prevent exceptions from being thrown from the child process, because it is lacking a console window.
You can use Convert.ToChar(ExeOutput.Read()) to convert the input to a valid KeyChar, mimicing the behavior of ReadKey() Also keep in mind Synchronous vs Asynchronous reads/writes. If you use BeginOutputReadLine() and read the stream Asynchronously, your while condition of P2.HasExited may become true before you read all of the input keys when using ExeOutput.Read()
.....
P2.Start();
StreamWriter ExeInput = P2.StandardInput;
StreamReader ExeOutput = P2.StandardOutput;
do
{
var k = P2.StandardOutput.Read();
var key = Convert.ToChar(k);
if (key == Convert.ToChar(ConsoleKey.Enter))
{
Console.WriteLine();
ExeInput.Write("\n");
}
else
ExeInput.Write(key);
} while (!P2.HasExited);
....
Fortunately, the streams will be buffered if the process has exited before you've read every line, so you may consider changing the condition to while(!P2.HasExited && !P2.StandardOutput.EndOfStream) if that fits what you're trying to accomplish.