I have checked all other solutions, nothing is working.
I am calling an asynchronous logging method from different button events.
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() => LoggerTest());
}
private async void button2_Click(object sender, EventArgs e)
{
await Task.Run(() => LoggerTest());
}
private async void LoggerTest()
{
for (int i = 0; i < 10000; i++)
{
Logger.Log(string.Format("Counter: {0}", i));
Thread.Sleep(10);
}
}
Log method uses StreamWriter
private void Log(string log)
{
if (!IsFileLocked(fullPath))
{
using (StreamWriter file = new StreamWriter(fullPath, append: true))
{
file.WriteLine(log);
file.Close(); // I know this is unnecessary in the using block
}
}
}
private bool IsFileLocked(string file)
{
try
{
using (var stream = File.OpenRead(file))
return false;
}
catch (IOException)
{
return true;
}
}
When I click on button1 and button2, the below exception is caught:
System.IO.IOException: 'The process cannot access the file 'C:..\x.txt' because it is being used by another process.'
try something like this
private static readonly object locker = new object();
lock (locker)
{
using (FileStream fileStream = new FileStream("FilePath"), FileMode.Append))
{
using (StreamWriter writer = new StreamWriter(fileStream))
{
writer.WriteLine(log);
}
}
}
lock keyword will lock the stream writer till the current writer process is finished
Related
The BackgroundWorker's progressbar is not updated while doing some tasks. What I would like to reach is progressbar moving while iterating through each file in DirectoryInfo. Suppose we have 20 files of ".sql" while first file completed it should be 5%, 10% and etc.
Here is my code.
private void CSV_Click(object sender, RoutedEventArgs e)
{
try
{
btnExtract.IsEnabled = false;
workerextract.RunWorkerAsync();
}
catch (Exception ex)
{
System.Windows.MessageBox.Show(ex.Message);
}
}
private void workerextract_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
try
{
this.Dispatcher.Invoke(() =>
{
DirectoryInfo di = new DirectoryInfo(txtQueryfolder.Text);
files = di.GetFiles("*.sql").Count();
currentfile = 0;
foreach (FileInfo fi in di.GetFiles("*.sql"))
{
// Open the text file using a stream reader.
using (StreamReader sr = new StreamReader(fi.FullName))
{
// Read the stream to a string, and write the string to the console.
string line = sr.ReadToEnd();
//System.Windows.MessageBox.Show(line);
ExtractToCSV(line, System.IO.Path.GetFileNameWithoutExtension(fi.Name));
currentfile++;
}
int percentage = (currentfile + 1) * 100 / files;
workerextract.ReportProgress(percentage);
}
});
}
catch(Exception ex)
{
System.Windows.MessageBox.Show(ex.Message);
}
}
private void workerextract_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
progressBarExtract.Value = e.ProgressPercentage;
}
private void workerextract_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
btnExtract.IsEnabled = true;
System.Windows.MessageBox.Show("CSV Data extraction finished!");
}
I found that
private void workerextract_ProgressChanged(object sender,
System.ComponentModel.ProgressChangedEventArgs e)
is called once at the end when 100%.
Also,
private void workerextract_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
never called as I do not see Message Box at the end.
So, I think I am doing something wrong here, could you please direct me on right way?
The problem was in wrapping whole DoWork inside Dispatcher.Invoke.
I need to wrap only those code where it is interacting with UI.
So I changed the code appropriately and it works.
private void workerextract_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
try
{
this.Dispatcher.Invoke(() =>
{
di = new DirectoryInfo(txtQueryfolder.Text);
});
files = di.GetFiles("*.sql").Count();
currentfile = 0;
foreach (FileInfo fi in di.GetFiles("*.sql"))
{
// Open the text file using a stream reader.
using (StreamReader sr = new StreamReader(fi.FullName))
{
// Read the stream to a string, and write the string to the console.
string line = sr.ReadToEnd();
this.Dispatcher.Invoke(() =>
{
//System.Windows.MessageBox.Show(line);
ExtractToCSV(line, System.IO.Path.GetFileNameWithoutExtension(fi.Name));
});
currentfile++;
}
int percentage = (currentfile + 1) * 100 / files;
workerextract.ReportProgress(percentage);
}
}
catch(Exception ex)
{
System.Windows.MessageBox.Show(ex.Message);
}
}
Thanks to all for showing the direction.
Using this.Dispatcher.Invoke in the BackgroundWorker's DoWork event you are executing the whole operation in the UI thread; which is what BackgroundWorker born to avoid to do.
Also, you get an error unwrapping your code from the dispatcher because you are accessing an UI object, which is txtQueryfolder.
Just use:
private void workerextract_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
string queryFolder = e.Argument.ToString();
try
{
DirectoryInfo di = new DirectoryInfo(queryFolder);
files = di.GetFiles("*.sql").Count();
currentfile = 0;
foreach (FileInfo fi in di.GetFiles("*.sql"))
{
// Open the text file using a stream reader.
using (StreamReader sr = new StreamReader(fi.FullName))
{
// Read the stream to a string, and write the string to the console.
string line = sr.ReadToEnd();
//System.Windows.MessageBox.Show(line);
// ExtractToCSV shouldn't access to a UI object.
ExtractToCSV(line, System.IO.Path.GetFileNameWithoutExtension(fi.Name));
currentfile++;
}
int percentage = (currentfile + 1) * 100 / files;
workerextract.ReportProgress(percentage);
}
}
catch (Exception ex)
{
// Don't use MessageBox in a thread different from the UI one. Just set the result (e.Result) and get that in the RunWorkerCompleted event.
// System.Windows.MessageBox.Show(ex.Message);
}
}
When you call the RunWorkerAsync method just add the parameter like below:
workerextrac.RunWorkerAsync(txtQueryfolder.Text);
I'm writing a windows service (C#) that does a task repetitively. I'm using a thread to complete my requirement. Now I need to maintain a log file that keeps logs regarding the operation.
My service class is as follow
public partial class CPEService : ServiceBase
{
static ServiceBot bot = new ServiceBot();
static ProgramLog logger = new ProgramLog();//ProgramLog Object
private static bool state = true;
//private static int count = 1;
//private System.Timers.Timer timer;
public CPEService()
{
InitializeComponent();
}
internal void TestStartupAndStop()
{
Thread workerThread = new Thread(loopTrough);
workerThread.Start();
}
protected override void OnStart(string[] args)
{
Thread workerThread = new Thread(loopTrough);
workerThread.Start();
}
private void loopTrough()
{
logger.log("Thread fired");
while (state)
{
logger.log("Thread fired"); //This is not Working
bot.start();
Thread.Sleep(180000);
}
}
protected override void OnStop()
{
state = false;
}
}
I have a separate class call "ProgramLog" to handle all the log related operations.This is that class.
public class ProgramLog
{
string fileName = "";//Global variable to store file name
#region method to handle usual log records
public void log(string text)//create normal Log text
{
fileName = "Log\\" + DateTime.Now.Date.ToString("d").Replace('/', '_') + ".txt";
if (File.Exists(AppDomain.CurrentDomain.BaseDirectory+fileName))
{
using (FileStream fs = new FileStream(AppDomain.CurrentDomain.BaseDirectory + fileName, FileMode.Append))
using (TextWriter tw = new StreamWriter(fs))
{
tw.WriteLine(text);
tw.Flush();
tw.Close();
fs.Close();
}
}
else
{
createFolder();
log(text);
}
}
#endregion
#region log Error record
public void logError(string text, string className,int LineNumber, string Stacktrace)//create error text
{
fileName = "Log\\" + DateTime.Now.Date.ToString("d").Replace('/', '_') + ".txt";
if (File.Exists(AppDomain.CurrentDomain.BaseDirectory + fileName))
{
using (FileStream fs = new FileStream(AppDomain.CurrentDomain.BaseDirectory + fileName, FileMode.Append))
using (TextWriter tw = new StreamWriter(fs))
{
tw.WriteLine("**************************ERROR****************************");
tw.WriteLine(text);
tw.WriteLine("In Class :{0}", className);
tw.WriteLine("In Line :{0}", LineNumber);
tw.WriteLine("ERROR :{0}",Stacktrace);
tw.WriteLine("***********************************************************");
}
}
else
{
createFolder();
logError(text,className,LineNumber,Stacktrace);
}
}
#endregion
#region create folder to store log files
public void createFolder()//create a folder for Log files
{
try
{
if (!Directory.Exists(AppDomain.CurrentDomain.BaseDirectory + "Log"))
{
string folderName = "Log";
Directory.CreateDirectory(AppDomain.CurrentDomain.BaseDirectory + folderName);
FileStream fs = new FileStream(AppDomain.CurrentDomain.BaseDirectory + fileName, FileMode.Create);
StreamWriter sr = new StreamWriter(fs);
sr.Flush();
sr.Close();
fs.Close();
}
else
{
FileStream fs = new FileStream(AppDomain.CurrentDomain.BaseDirectory + fileName, FileMode.Create);
StreamWriter sr = new StreamWriter(fs);
sr.Flush();
sr.Close();
fs.Close();
}
}
catch (Exception e)
{
Console.WriteLine(e.StackTrace);
}
}
#endregion
}
According to the above class, When I start the service it needs to create folder call "Log" where it does not exists, then it creates a text file inside that folder and lastly it starts to create the log entries.
Even though the thread is working correctly it never touches the "ProgramLog" methods. I checked by directly calling the method "loopTrough". then its working fine.
Please help me to resolve this bug.
Thank you
You declare a Thread workerThread = new Thread(loopTrough);, but you don't start this Thread. Just call workerThread.Start().
I'm developping an app in C# using microsoft visual studio (windows form).
What i want to do is to manage different environment through one GUI.
Thus, my gui have to start asynchronously some process (which are commandline applications).
The problem is that I can get the standard output of the process only once it's finished, meaning I can't show what the process is doing in runtime.
since the applications I want to run can take quite a long runtime (uploading big files ...) i would like to display the process output in runtime.
Thus, i created a backgroundworker to separate my gui from the process, and i tried to use a temporary file where the process output is written.
then using a FileSystemWatcher, I could use the "change" event to display the messages in my GUI.
My problem is that since the temporary file is open for writting, i can't read from it at the same time.
Here is my code, does anyone have a way to bypass this problem ? or an other way to do it ?
public partial class Form1 : Form
{
Boolean done = false;
private FileSystemWatcher observateur;
public Form1()
{
InitializeComponent();
// delete the temporary file if existing
if (System.IO.File.Exists("C:\\testoutput.txt"))
{
try
{
System.IO.File.Delete("C:\\testoutput.txt");
}
catch (System.IO.IOException exept)
{
Console.WriteLine(exept.Message);
return;
}
}
File.Create("C:\\testoutput.txt");
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler
(backgroundWorker1_ProgressChanged);
observateur = new FileSystemWatcher();
observateur.Filter = "C:\\testoutput.txt";
observateur.Changed += new FileSystemEventHandler(this.OnChanged);
observateur.Created += new FileSystemEventHandler(this.OnCreate);
}
private void OnChanged(object source, FileSystemEventArgs e)
{
// I tried to bypass the problem of having the file opened by copying it but i doesn't work
File.Copy("C:\\testouput.txt", "C:\\TEMP.txt", true);
}
private void OnCreate(object source, FileSystemEventArgs e)
{
Console.WriteLine("Created");
}
private void button3_Click(object sender, EventArgs e)
{
string outputworker = "";
backgroundWorker1.RunWorkerAsync(outputworker);
while (!done)
{
string text = System.IO.File.ReadAllText("C:\\TEMP.txt");
Thread.Sleep(200);
}
}
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
outputTextArea.Text = "Processing......" + progressBar1.Value.ToString() + "%";
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
string[] args = { "/k " };
string outputWork = e.Argument as string;
backgroundWorker1.ReportProgress(10);
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.WorkingDirectory = "C:\\XXXXXXXXXX";
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "cmd.exe";
int nArgs = args.Length;
if (nArgs > 0)
{
process.StartInfo.Arguments = args[0];
}
for (int i = 1; i < nArgs; i++)
{
process.StartInfo.Arguments = String.Concat(process.StartInfo.Arguments, " && ", args[i]);
}
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
backgroundWorker1.ReportProgress(20);
process.Start();
backgroundWorker1.ReportProgress(40);
System.IO.StreamWriter sIn = process.StandardInput;
sIn.WriteLine("ExternalCommandLineApp1.exe >> C:\\testoutput.txt");
backgroundWorker1.ReportProgress(60);
sIn.WriteLine("ExternalCommandLineApp1.exe >> C:\\testoutput.txt");
System.IO.StreamReader sOut = process.StandardOutput;
backgroundWorker1.ReportProgress(90);
sIn.WriteLine("EXIT");
outputWork = sOut.ReadToEnd();
process.Close();
backgroundWorker1.ReportProgress(100);
e.Result = outputWork;
done = true;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
string output = e.Result as string;
//outputTextArea.Text = output;
}
}
This is not the best way as mentioned in other answers, but it still can work successfully.
You can open a file for reading/writing without blocking other reads/writes. Just use File.Open instead of helper methods and provide additional parameters (FileMode and FileShare)
Here is a complete example. Note that one thread keeps file opened for writing and second thread opens and closes file every time and reads all lines:
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string fileName = "c:\\_data\\temp.txt";
Task writer = new Task(() => {
using (FileStream fs = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite))
using (StreamWriter sw = new StreamWriter(fs))
{
for (int i = 0; i < 50; i++ )
{
sw.WriteLine(DateTime.Now.Millisecond.ToString());
sw.Flush();
Thread.Sleep(500);
}
}
});
Task reader = new Task(() => {
for (int i = 0; i < 50; i++)
{
Thread.Sleep(500);
Console.WriteLine("Read again");
if (File.Exists(fileName))
{
using (FileStream fs = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
using (StreamReader r = new StreamReader(fs))
{
while (!r.EndOfStream)
{
Console.WriteLine(r.ReadLine());
}
}
}
}
});
writer.Start();
reader.Start();
writer.Wait();
reader.Wait();
}
}
}
The simplest way with what you've already got is to exploit the UserState you can pass with the BackgroundWorker.
In the backgroundWorker1_DoWork method, you can use
backgroundWorker1.ReportProgress(0, "Whatever text you want to send right now.");
And in backgroundWorker1_ProgressChanged, you can read the message and put it in the text box like this:
outputTextArea.AppendText((e.UserState as string) + "\r\n");
This is a bit inefficient, but it should be much safer and faster than your original solution anyway.
In .NET, you've got many options of passing data between threads. If you want to learn more about the concepts, problems and solutions of multi-threading, you can give this a go: http://www.albahari.com/threading/
You can get the Standard Output of processes using System.Diagnostics.Process StandardOutput property (it's a Stream).
http://msdn.microsoft.com/en-us/library/vstudio/system.diagnostics.process.standardoutput(v=vs.90).aspx
I suggest you use Windows Communications Foundation to do this.
Following is a complete example.
There are two helper classes that you would normally put into a class library for reuse, class WcfServiceHost<T> and class WcfServiceProxy<T>.
This is a console app which you should run from the command line twice, passing a parameter of monitor for the first instance you start, and worker for the second instance.
Run it from the command like like this (assuming the app is called ConsoleApp1.exe):
start ConsoleApp1.exe monitor
start ConsoleApp1.exe worker
and then look at the output. The monitor instance is waiting for progress reports from the worker. The worker instance is reporting the progress, effectively by calling a function in the monitor instance (RPC, or Remote Procedure Call).
Here's the complete code. You will need to reference System.ServiceModel:
using System;
using System.Diagnostics;
using System.ServiceModel;
using System.Threading;
namespace Demo
{
[ServiceContract]
interface IProgressReporter
{
[OperationContract]
void ReportProgress(double percentComplete, string message);
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
sealed class Monitor: IProgressReporter
{
public void ReportProgress(double percentComplete, string message)
{
Console.WriteLine("Monitor received progress - Completed {0}%: {1}", percentComplete, message);
if (percentComplete == 100)
{
Program.ReportFinished();
}
}
}
public sealed class WcfServiceHost<T>: IDisposable where T: class
{
public WcfServiceHost(T service, string wcfEndpointAddress)
{
_service = service;
_wcfEndpointAddress = wcfEndpointAddress;
var serviceHost = new ServiceHost(service);
serviceHost.AddServiceEndpoint(typeof(T), new NetNamedPipeBinding(), wcfEndpointAddress);
serviceHost.Open();
_serviceHost = serviceHost;
}
public T Service
{
get
{
return _service;
}
}
public string WcfEndpointAddress
{
get
{
return _wcfEndpointAddress;
}
}
/// <summary>Disposal.</summary>
public void Dispose()
{
if (_serviceHost != null)
{
try
{
_serviceHost.Close();
}
catch (Exception exception) // Don't allow exceptions to escape from Dispose().
{
Trace.WriteLine("There was an exception while closing the host: " + exception.Message);
}
}
}
private readonly T _service;
private readonly string _wcfEndpointAddress;
private readonly ServiceHost _serviceHost;
}
public sealed class WcfServiceProxy<T>: IDisposable where T: class
{
public WcfServiceProxy(string wcfEndpointAddress)
{
_wcfEndpointAddress = wcfEndpointAddress;
_channelFactory = new ChannelFactory<T>(new NetNamedPipeBinding(), _wcfEndpointAddress);
_service = _channelFactory.CreateChannel();
_comms = _service as ICommunicationObject;
if (_comms == null)
{
throw new InvalidOperationException("proxy does not implement ICommunicationObject.");
}
}
public T Service
{
get
{
return _service;
}
}
public string WcfEndpointAddress
{
get
{
return _wcfEndpointAddress;
}
}
public void Dispose()
{
closeComms();
closeChannelFactory();
}
private void closeComms()
{
try
{
_comms.Close();
}
catch (CommunicationException exception) // Not closed - call Abort to transition to the closed state.
{
Debug.WriteLine("CommunicationException while closing ICommunicationObject: " + exception.Message);
_comms.Abort();
}
catch (TimeoutException exception) // Not closed - call Abort to transition to the closed state.
{
Debug.WriteLine("TimeoutException while closing ICommunicationObject: " + exception.Message);
_comms.Abort();
}
catch (Exception exception) // Not closed - call Abort to transition to the closed state.
{
Trace.WriteLine("Unexpected exception while closing ICommunicationObject: " + exception.Message);
_comms.Abort();
}
}
private void closeChannelFactory()
{
try
{
_channelFactory.Close();
}
catch (CommunicationException exception) // Not closed - call Abort to transition to the closed state.
{
Debug.WriteLine("CommunicationException while closing ChannelFactory: " + exception.Message);
_channelFactory.Abort();
}
catch (TimeoutException exception) // Not closed - call Abort to transition to the closed state.
{
Debug.WriteLine("TimeoutException while closing ChannelFactory: " + exception.Message);
_channelFactory.Abort();
}
catch (Exception exception) // Not closed - call Abort to transition to the closed state.
{
Trace.WriteLine("Unexpected exception while closing ChannelFactory: " + exception.Message);
_channelFactory.Abort();
}
}
private readonly T _service;
private readonly string _wcfEndpointAddress;
private readonly ChannelFactory<T> _channelFactory;
private readonly ICommunicationObject _comms;
}
internal static class Program
{
static void Main(string[] args)
{
if (args.Length > 0 && args[0] == "worker")
runWorker();
else
runMonitor();
Console.WriteLine("\nEnded. Press a key to exit.");
Console.ReadKey();
}
public static void ReportFinished()
{
finished.Set();
}
static void runMonitor()
{
using (new WcfServiceHost<IProgressReporter>(new Monitor(), SERVICE_PIPE_NAME))
{
finished.WaitOne();
}
}
static void runWorker()
{
using (var proxy = new WcfServiceProxy<IProgressReporter>(SERVICE_PIPE_NAME))
{
for (int i = 0; i <= 100; ++i)
{
Thread.Sleep(100);
Console.WriteLine("Worker reporting progress: Completed {0}%: {1}", i, i);
proxy.Service.ReportProgress(i, i.ToString());
}
}
}
private static ManualResetEvent finished = new ManualResetEvent(false);
private const string SERVICE_PIPE_NAME = "net.pipe://localhost/MyServicePipeName";
}
}
thanks to you i managed to do what i wanted ^^
Since it took me quite some time to search/debug, I share my solution.
I used a temporary text file, so it's not very "professional" but it works.
To run the process, you have to call :
string[] args = { "/c cmd1", "cmd2" , "cmd3"};
backgroundWorker1.RunWorkerAsync(args);
(sync on a button pressed event for example)
public partial class Form1 : Form
{
string fileName = "c:\\temp\\tempoutput.txt";
public Form1()
{
InitializeComponent();
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler
(backgroundWorker1_ProgressChanged);
}
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// This function fires on the UI thread so it's safe to edit the UI control directly
progressBar1.Value = e.ProgressPercentage;
readTempFile();
//outputTextArea.Text = "Processing......" + progressBar1.Value.ToString() + "%";
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// command line
string[] args = e.Argument as string[];
backgroundWorker1.ReportProgress(2);
try
{
FileStream fs = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
StreamWriter sw = new StreamWriter(fs);
sw.WriteLine("### Starting the process : ###");
sw.Flush();
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.WorkingDirectory = "WorkdirPath";
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "cmd.exe";
// create the command line
int nArgs = args.Length;
if (nArgs > 0)
{
process.StartInfo.Arguments = args[0];
}
for (int i = 1; i < nArgs; i++)
{
process.StartInfo.Arguments = String.Concat(process.StartInfo.Arguments, " && ", args[i]);
}
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
backgroundWorker1.ReportProgress(5);
process.Start();
backgroundWorker1.ReportProgress(10);
System.IO.StreamWriter sIn = process.StandardInput;
System.IO.StreamReader sOut = process.StandardOutput;
backgroundWorker1.ReportProgress(15);
int timeCount = 15;
string tempOut = "";
while (!sOut.EndOfStream)
{
tempOut = sOut.ReadLine();
sw.WriteLine(tempOut);
sw.Flush();
if (timeCount < 90)
{
// increasing the progress bar value.
//timeCount += 1;
}
backgroundWorker1.ReportProgress(timeCount);
}
sw.WriteLine("Closing process");
sw.Flush();
process.Close();
backgroundWorker1.ReportProgress(100);
}
catch (System.IO.IOException exept)
{
Console.WriteLine(exept.Message);
return;
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
readTempFile();
}
private void readTempFile()
{
try
{
FileStream fs = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
StreamReader r = new StreamReader(fs);
string output = r.ReadToEnd();
outputTextArea.Text = output;
}
catch (System.IO.IOException exept)
{
Console.WriteLine(exept.Message);
return;
}
}
}
Ok, so I'm attempting to create a simple Chat application over TCP/IP for a group of friends of mine who play DnD online. Eventually I want to add more features, but for now I just want the chat to work!!
Here is the code I have for the Main Server
class MainServer
{
IPAddress m_address = IPAddress.Parse("127.0.0.1");
Int32 m_port = 5550;
public static Hashtable userNicknames = new Hashtable(50);
public static Hashtable connectionToNick = new Hashtable(50);
public MainServer()
{
TcpListener listener = new TcpListener(m_address, m_port);
Thread listenThread = new Thread(new ParameterizedThreadStart(StartListening));
listenThread.Start(listener);
Console.WriteLine("Listening for incoming connection requests...");
}
private void StartListening(Object listener)
{
TcpListener server = (TcpListener)listener;
ClientCommCenter commC;
server.Start();
while (true)
{
if (server.Pending())
{
TcpClient client = server.AcceptTcpClient();
Console.WriteLine("Client has connected...");
commC = new ClientCommCenter(client);
}
}
}
public static void SendSystemMessage(string msg)
{
StreamWriter writer;
TcpClient[] connectedClients = new TcpClient[MainServer.userNicknames.Count];
MainServer.userNicknames.Values.CopyTo(connectedClients, 0);
for (int ii = 0; ii < connectedClients.Length; ii++)
{
try
{
if (msg.Trim().Equals(String.Empty))
continue;
writer = new StreamWriter(connectedClients[ii].GetStream());
writer.WriteLine("Message from server: " + msg);
writer.Flush();
writer = null;
}
catch (Exception e)
{
MainServer.userNicknames.Remove(MainServer.connectionToNick[connectedClients[ii]]);
MainServer.connectionToNick.Remove(connectedClients[ii]);
}
}
}
public static void SendMessageToAll(string nickname, string msg)
{
StreamWriter writer;
TcpClient[] connectedClients = new TcpClient[MainServer.userNicknames.Count];
MainServer.userNicknames.Values.CopyTo(connectedClients, 0);
for (int ii = 0; ii < connectedClients.Length; ii++)
{
try
{
if (msg.Trim().Equals(String.Empty))
continue;
writer = new StreamWriter(connectedClients[ii].GetStream());
writer.WriteLine(nickname + ": " + msg);
writer.Flush();
writer = null;
}
catch (Exception e)
{
String user = (string)MainServer.connectionToNick[connectedClients[ii]];
SendSystemMessage("ATTENTION: " + user + " has disconnected from chat");
MainServer.userNicknames.Remove(user);
MainServer.connectionToNick.Remove(connectedClients[ii]);
}
}
}
}
Here is the main communication class, used separately by each client
class ClientCommCenter
{
TcpClient m_client;
StreamReader m_reader;
StreamWriter m_writer;
String m_nickname;
public ClientCommCenter(TcpClient client)
{
m_client = client;
Thread chatThread = new Thread(new ThreadStart(StartChat));
chatThread.Start();
}
private String GetNick()
{
m_writer.WriteLine("Enter a nickname to begin.");
m_writer.Flush();
return m_reader.ReadLine();
}
private void StartChat()
{
m_reader = new StreamReader(m_client.GetStream());
m_writer = new StreamWriter(m_client.GetStream());
m_writer.WriteLine("Connected to DnD Chat!!");
m_nickname = GetNick();
while (MainServer.userNicknames.Contains(m_nickname))
{
m_writer.WriteLine("ERROR!!! Username already in use");
m_nickname = GetNick();
}
MainServer.userNicknames.Add(m_nickname, m_client);
MainServer.connectionToNick.Add(m_client, m_nickname);
MainServer.SendSystemMessage("****** " + m_nickname + " ****** has joined the chat!");
m_writer.WriteLine("Now connected....");
m_writer.Flush();
Thread startChatting = new Thread(new ThreadStart(runChat));
startChatting.Start();
}
private void runChat()
{
try
{
String clientMessage = String.Empty;
while(true){
clientMessage = m_reader.ReadLine();
MainServer.SendMessageToAll(m_nickname, clientMessage);
}
}
catch(Exception e)
{
Console.WriteLine(e);
}
}
}
And finally, here is the code for the Client class:
public partial class MainForm : Form
{
[DllImport("kernel32.dll")]
private static extern void ExitProcess(int a);
TcpClient client;
StreamReader m_reader;
StreamWriter m_writer;
public MainForm()
{
InitializeComponent();
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = false;
Application.Exit();
if (m_reader != null)
{
m_reader.Dispose();
}
ExitProcess(0);
}
private void MainForm_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
SendChat();
}
}
private void SendChat()
{
TextBox txtChat = (TextBox)chatEntry;
if (chatEntry.Lines.Length >= 1)
{
m_writer.WriteLine(txtChat.Text);
m_writer.Flush();
chatEntry.Text = String.Empty;
chatEntry.Lines = null;
}
}
private void RunChat()
{
StreamReader reader = new StreamReader(client.GetStream());
while (true)
{
Application.DoEvents();
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker( delegate{
RunChat();
}));
}
if (reader.Peek() > 0)
{
chatDisplay.AppendText(reader.ReadLine() + "\r\n");
chatDisplay.SelectionStart = chatDisplay.Text.Length;
}
}
}
private void toolstripConnectButton_Click(object sender, EventArgs e)
{
client = new TcpClient("127.0.0.1", 5550);
m_writer = new StreamWriter(client.GetStream());
m_reader = new StreamReader(client.GetStream());
Thread chatThread = new Thread(new ThreadStart(RunChat));
chatThread.Start();
while (true)
{
Application.DoEvents();
}
}
private void sendButton_Click(object sender, EventArgs e)
{
SendChat();
}
}
The problem that I am having with the above code is this: I can connect to the running server perfectly fine, and I am correctly prompted by the server that I have connected, and it then prompts me for a nickname.
I type the nickname into the text box and press send. After this occurs however, I stop receiving messages from the server all together. Literally nothing. I can even spam the connect button and it constantly shows up with the same two messages:
"Connected"
"Enter a nickname"
I have been trying to figure this out for close to 5 hours now, and I simply have no idea what is going on. I have a feeling it is something incredibly simple, as the solution is ALWAYS simple.
So, generous people of SO, can you figure out my problem? Why does my streamreader and streamwriter suddenly stop working?!?!?!
Two things:
First, skip the if (reader.Peek() > 0). Just call reader.ReadLine(); this will block until you have a line available. I am not sure why, but even after sending the message, Peek is returning -1, but ReadLine returns a line at that point, fixing the problem. Anyway, spinning around on Application.DoEvents() is not helping matters.
(Similarly, you can skip if (server.Pending())).
Second, your use of Invoke is faulty; you should not be "Invoking" RunChat() because that is the method that repeatedly polls the stream for new data. This means you will run the entire method on the UI thread, which is precisely what you want to avoid. The UI is busy pumping the Windows message queue. You should "Invoke" only the code that modifies the control's properties.
(I suspect that is why you found it necessary to use Application.DoEvents() anyway. You shouldn't need it if you are handling your threading correctly.)
(Also, the first thing you should do is to check InvokeRequired. As your method is now, you're creating a StreamReader that you can never use. There are other places where you do that, but that's off topic.)
Here are two suggestions:
private void RunChat()
{
StreamReader reader = new StreamReader(client.GetStream());
Delegate invoker = new Action<string>(AppendChatText);
while (true)
Invoke(invoker, reader.ReadLine());
}
or, to use the more classic "invoke" pattern:
private void RunChat()
{
StreamReader reader = new StreamReader(client.GetStream());
while (true)
AppendChatText(reader.ReadLine());
}
private void AppendChatText(string text)
{
if (this.InvokeRequired)
{
this.Invoke((Action<string>)AppendChatText, text);
return;
}
chatDisplay.AppendText(text + "\r\n");
chatDisplay.SelectionStart = chatDisplay.Text.Length;
}
The first has the advantage of creating only one Delegate object; the second creates a new one each time.
Finally, this is a very C# 1.2 approach to the problem. A more up-to-date approach would use async/await to avoid creating all those threads (not to mention System.Collections.Generic.Dictionary<,> instead of HashTable).
I want to process the http request in thread, the code as follow where the problem is that OutputStream (as noted in codes) can not be writen. When I put this process in main thread, it's no problem. Can you give me any advice?
public partial class MainWindow : Window
{
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var server = new HttpServer();
try
{
server.EndPoint = new IPEndPoint(127.0.0.0, 80); //set webServer para
server.Start(); //start webServer
server.RequestReceived += server_RequestReceived; //register the event
}
catch (Exception ex)
{
return;
}
}
private void server_RequestReceived(object sender, HttpRequestEventArgs e)
{
var dataProcess = new Action<HttpRequestEventArgs>(DataProcess);
Dispatcher.BeginInvoke(dataProcess,e); //start thread
}
private void DataProcess(HttpRequestEventArgs e)
{
StreamReader sr = new StreamReader(#"c:\test.txt");
string text = sr.ReadToEnd();
using (var writer = new StreamWriter(e.Response.OutputStream)) //**Cannot write stream**
{
writer.Write(text);
}
sr.Close();
}
}
I think your request is being close before the new thread runs. What you can do is to execute the whole request in the new thread. Something like:
public partial class MainWindow : Window
{
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var start = new Action<HttpRequestEventArgs>(Start);
Dispatcher.start(start,e); //start thread
}
private void Start(HttpRequestEventArgs e)
{
var server = new HttpServer();
server.EndPoint = new IPEndPoint(127.0.0.0, 80); //set webServer para
server.Start(); //start webServe
server.RequestReceived += server_RequestReceived; //register the event
}
private void server_RequestReceived(object sender, HttpRequestEventArgs e)
{
StreamReader sr = new StreamReader(#"c:\test.txt");
string text = sr.ReadToEnd();
using (var writer = new StreamWriter(e.Response.OutputStream)) //**Cannot write stream**
{
writer.Write(text);
}
sr.Close();
}
}
Most likley e.Response.OutputStream is write-only stream which contains response received from the server.
Depending on what you want to do you may either write to request stream or read from OutputStream.
Note: you are using some custom classes, so suggestion is based purely on method names.