Winforms background worker gets stuck - c#

I am trying to build a simple code that joins csv files into one distinct file, but my background worker seems to have a mind of its own and my code gets stuck every time.
Here is my code for joining the file using the background worker:
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
try
{
if (string.IsNullOrEmpty(saveFilePath))
{
this.Invoke(new MethodInvoker(delegate
{
btnBrowseSave.PerformClick();
}));
}
if (!string.IsNullOrEmpty(saveFilePath))
{
if (dragEventArgs != null)
files = (string[])dragEventArgs.Data.GetData(DataFormats.FileDrop);
int filesCount = 0, rowsCount = 0;
foreach (string file in files)
{
filesCount++;
int fileTotalLines = File.ReadAllLines(file).Length;
this.Invoke(new MethodInvoker(delegate
{
lblFileName.Text = "Loading file: " + file.Substring(file.LastIndexOf("\\") + 1);
lblTotalFiles.Text = "File " + filesCount + " of " + files.Length;
}));
using (StreamReader reader = new StreamReader(file))
{
using (StreamWriter writer = new StreamWriter(saveFilePath))
{
while (!reader.EndOfStream)
{
try
{
while (stopPosition > rowsCount)
{
reader.ReadLine();
rowsCount++;
}
string email = reader.ReadLine().Trim();
if (!string.IsNullOrEmpty(email) && !dicEmails.ContainsKey(email))
{
dicEmails.Add(email, null);
writer.WriteLine(email);
}
rowsCount++;
stopPosition++;
backgroundWorker.ReportProgress((rowsCount * 100 / fileTotalLines), (int)ProgressType.Row);
if (backgroundWorker.CancellationPending)
return;
}
catch (Exception ex)
{
hadReadErrors = true;
}
}
}
}
backgroundWorker.ReportProgress(0, (int)ProgressType.Row);
backgroundWorker.ReportProgress((filesCount * 100 / files.Length), (int)ProgressType.File);
}
}
}
catch (Exception ex)
{
hadReadErrors = true;
MessageBox.Show(ex.Message);
}
finally
{
backgroundWorker.Dispose();
}
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
try
{
switch ((int)e.UserState)
{
case (int)ProgressType.Row:
lblFileProgress.Text = e.ProgressPercentage + "%";
fileProgressBar.Value = e.ProgressPercentage;
break;
case (int)ProgressType.File:
lblTotalProgress.Text = e.ProgressPercentage + "%";
totalProgressBar.Value = e.ProgressPercentage;
break;
}
}
catch (Exception ex) { }
}
When I run in debug mode and go with the debugger I don't see any problems, but when I let the code run it self it gets stuck and crashes.
Can someone PLEASE help me and tell me what am I missing out here ?
Here is the exception:
Managed Debugging Assistant 'ContextSwitchDeadlock' has detected a problem in
'C:\Users\Develop\Desktop\ExcelBuilder\ExcelBuilder\bin\Debug\ExcelBuilder.vshost.exe'.
Additional information: The CLR has been unable to transition from COM context 0x90fb78
to COM context 0x90fc30 for 60 seconds. The thread that owns the destination
context/apartment is most likely either doing a non pumping wait or processing a very
long running operation without pumping Windows messages. This situation generally has
a negative performance impact and may even lead to the application becoming non
responsive or memory usage accumulating continually over time. To avoid this problem,
all single threaded apartment (STA) threads should use pumping wait primitives
(such as CoWaitForMultipleHandles) and routinely pump messages during long running operations.

I did a small example of your program, trying to guess what it must do (https://github.com/anderson-rancan/stackoverflow_28798348, drag and drop 4 files to the groupbox, lorem?.csv), and there is a few things that you should consider:
never try/catch a unknown exception, every exception or something you cannot deal with (https://msdn.microsoft.com/en-us/library/ms182137.aspx)
when using a BackgroundWorker on a form, use the "sender" for references to it, it's a thread safe object to your method (https://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx)
maybe you are updating too fast your form, change your Invoke method to BeingInvoke, and do the update async (https://msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.110).aspx)
So, just fixing that was possible to run it, like this:
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bckw = (BackgroundWorker)sender; // Recommended way, thread safe
try
{
if (string.IsNullOrEmpty(saveFilePath))
{
this.Invoke(new MethodInvoker(delegate
{
btnBrowseSave.PerformClick();
}));
}
if (!string.IsNullOrEmpty(saveFilePath))
{
if (dragEventArgs != null)
files = (string[])dragEventArgs.Data.GetData(DataFormats.FileDrop);
int filesCount = 0, rowsCount = 0;
foreach (string file in files)
{
filesCount++;
double fileTotalLines = File.ReadAllLines(file).Length;
this.BeginInvoke(new MethodInvoker(delegate
{
lblFileName.Text = "Loading file: " + file.Substring(file.LastIndexOf("\\") + 1);
lblTotalFiles.Text = "File " + filesCount + " of " + files.Length;
})); // Invoke async, way too fast this...
using (StreamReader reader = new StreamReader(file))
{
using (StreamWriter writer = new StreamWriter(saveFilePath))
{
while (!reader.EndOfStream)
{
try
{
while (stopPosition > rowsCount)
{
reader.ReadLine();
rowsCount++;
} // why are you using that? it won't get TRUE
string email = reader.ReadLine().Trim();
if (!string.IsNullOrEmpty(email) && !dicEmails.ContainsKey(email))
{
dicEmails.Add(email, null);
writer.WriteLine(email);
}
rowsCount++;
stopPosition++;
bckw.ReportProgress((int)Math.Round(rowsCount * 100 / fileTotalLines, 0), (int)ProgressType.Row);
if (bckw.CancellationPending)
return;
}
catch (Exception ex)
{
hadReadErrors = true;
throw; // Throw it again, or you won't know the Exception
}
}
}
}
bckw.ReportProgress(0, (int)ProgressType.Row);
bckw.ReportProgress((filesCount * 100 / files.Length), (int)ProgressType.File);
}
}
}
catch (Exception ex)
{
hadReadErrors = true;
MessageBox.Show(ex.Message);
}
finally
{
bckw.Dispose();
}
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//try
//{
switch ((int)e.UserState)
{
case (int)ProgressType.Row:
lblFileProgress.Text = e.ProgressPercentage + "%";
if (e.ProgressPercentage <= fileProgressBar.Maximum)
fileProgressBar.Value = e.ProgressPercentage;
break;
case (int)ProgressType.File:
lblTotalProgress.Text = e.ProgressPercentage + "%";
totalProgressBar.Value = e.ProgressPercentage;
break;
}
//}
//catch (Exception ex) { } // Don't catch everything
}
Finally, may I suggest another approach?
You're reading the file two times: one to get the number of lines, and another to read each line. Try to do this just once, you'll get a better result.

Related

C# Thread.Sleep(ms) Freezes UI and I can't use Alternative Options

I am Making C# WPF Application That contains CSV Writing.
But, I wanna give some delays. But When i use
Thread.Sleep(ms),
UI freezes and Windows says that This Program has been Stopped.
So, I found some Alternatives like
private static DateTime Delay(int MS)
{
DateTime ThisMoment = DateTime.Now;
TimeSpan duration = new TimeSpan(0, 0, 0, 0, MS);
DateTime AfterWards = ThisMoment.Add(duration);
while (AfterWards >= ThisMoment)
{
if (System.Windows.Application.Current != null)
{
System.Windows.Application.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, new Action(delegate { }));
}
ThisMoment = DateTime.Now;
}
return DateTime.Now;
}
But,when I use this method, The Program just Stops.
This is Button Code that makes, writes CSV File.
private void button_Click(object sender, RoutedEventArgs e)
{
try
{
System.IO.StreamWriter file = new System.IO.StreamWriter(#"New.csv");
int a = 1;
file.WriteLine("Delta, Theta");
while (a < 20)
{
file.WriteLine("{0}, {1}", TGLatestData.EegPowerDelta, TGLatestData.EegPowerTheta);
a++;
file.Close();
Thread.Sleep(3000); //Delay(3000); When I use Alternative way.
}
}
catch (Exception ex)
{
throw new ApplicationException("Broken", ex);
}
}
The Main Question that I want to know is how to delay some seconds without Freezing UI. Sorry for Bad Grammar.
Make your click handler async. Then you can use Task.Delay, which will not block the thread.
private async void button_Click(object sender, RoutedEventArgs e)
{
try
{
System.IO.StreamWriter file = new System.IO.StreamWriter(#"New.csv");
int a = 1;
file.WriteLine("Delta, Theta");
while (a < 20)
{
file.WriteLine("{0}, {1}", TGLatestData.EegPowerDelta, TGLatestData.EegPowerTheta);
a++;
file.Close();
await Task.Delay(3000);
}
}
catch (Exception ex)
{
throw new ApplicationException("Broken", ex);
}
}
By the way, here's one way to fix your exception:
private async void button_Click(object sender, RoutedEventArgs e)
{
const string fileName = #"New.csv";
try
{
File.AppendAllText(fileName, "Delta, Theta");
for (var a=1; a<20; a++)
{
var text = string.Format("{0}, {1}", TGLatestData.EegPowerDelta, TGLatestData.EegPowerTheta);
File.AppendAllText(fileName, text);
await Task.Delay(3000);
}
}
catch (Exception ex)
{
throw new ApplicationException("Broken", ex);
}
}
private async void CSVexport_Click(object sender, RoutedEventArgs e) //to use Await Task.Delay(ms) Option
{
try
{
SaveFileDialog saveAsfile = new SaveFileDialog(); //Save As File
saveAsfile.InitialDirectory = #"C:\";
saveAsfile.Title = "Save As File";
saveAsfile.Filter = "CSV Document(*.csv)|*.csv|All Files(*.*)|*.*";
saveAsfile.DefaultExt = "csv";
saveAsfile.AddExtension = true;
if (saveAsfile.ShowDialog() == System.Windows.Forms.DialogResult.OK) //Save As File Function
{
GaugeSignal.Value = 1;
if (TGLatestData.PoorSignal != -1)
{
var SignalQuality = Math.Round((200D - TGLatestData.PoorSignal) / 2D, 1);
if (SignalQuality < 0) SignalQuality = 0;
GaugeSignal.Value = SignalQuality;
TGLatestData.PoorSignal = TGLatestData.PoorSignal;
}
SQZero.Visibility = 0;
CSVexport.Visibility = Visibility.Hidden;
FileStream filestream = new FileStream(saveAsfile.FileName, FileMode.Create, FileAccess.Write);
System.Windows.MessageBox.Show("CSV Writing...\nIf you want to stop Writing CSV, Click SQ to 0 Button,", "CSV Writing...", MessageBoxButton.OK, MessageBoxImage.Warning);
System.IO.StreamWriter file = new System.IO.StreamWriter(filestream);
int time = 1000;
file.WriteLine("Signal Quality, Delta, Theta, Alpha1, Alpha2, Beta1, Beta2, Gamma1, Gamma2, Attention, Meditation, Mental Effort, Task Familiarity, Task Difficulty, Blink Strength");
while (true) //infinite loop
{
double Check_Signal_Zero_To_Stop = GaugeSignal.Value;
file.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12}, {13}, {14}", GaugeSignal.Value, TGLatestData.EegPowerDelta, TGLatestData.EegPowerTheta, TGLatestData.EegPowerAlpha1, TGLatestData.EegPowerAlpha2, TGLatestData.EegPowerBeta1, TGLatestData.EegPowerBeta2, TGLatestData.EegPowerGamma1, TGLatestData.EegPowerGamma2, TGLatestData.Attention, TGLatestData.Meditation, TGLatestData.MentalEffort, TGLatestData.TaskFamiliarity, TGLatestData.TaskDifficulty, TGLatestData.BlinkStrength);
await Task.Delay(time); //Delay Without FreezingUI (need async)
if (Check_Signal_Zero_To_Stop == 0)
{
file.Close(); //if you click SQ to 0, Do file.Close
while (true) await Task.Delay(1000000000);
}
}
}
}
catch (ApplicationException ex)
{
throw new ApplicationException("This Program is Get Broken", ex);
}
}
I changed private void to 'private async void' to use Await Task.Delay(ms) instead of Thread.Sleep(ms) and I also Made Stop Button to get out of infinite loop.
Here is SQ to 0 Button Source. If you Click this button, One variable(GaugeSignal.Value) will be 0 and it will stop Writing CSV and Saving modified file.
private async void SQzero_Click(object sender, RoutedEventArgs e)
{
int EndTime = 4000;
SQZero.Visibility = Visibility.Hidden;
System.Windows.MessageBox.Show("Stop Writing CSV... \nWait a Seconds..", "CSV Saving..", MessageBoxButton.OK, MessageBoxImage.Warning);
GaugeSignal.Value = 0;
await Task.Delay(EndTime); //Wait for Delay of CSVexport_btn
System.Windows.MessageBox.Show("CSV Saved!", "CSV Saved", MessageBoxButton.OK, MessageBoxImage.Warning);
CSVexport.Visibility = Visibility.Visible; //to Save another one Again
}
I was Foolish. I used file.Close(); in loop. So, It says IOException. I found other method to avoid them and Finally got it. Thanks to John Wu, Çöđěxěŕ to help me.

How to fix 'broken pipe' error when trying to write from NamedPipeClientStream to NamedPipeServerStream

I have a program, that has to comunicate with another program that is called from the first.
I have managed to send the required data from the first to the second program using NamedPipes.
When the second program closes, i need to send some data back to the first program. I set up a NamedPipeServerStream in the first program and from the Closing event of the second program a NamedPipeClientStream. Now when i try to write from ClientStream i get a broken pipe error.
This is the code from the first program:
private void CreateOverlay(object sender, EventArgs e)
{
if (oinstallfolder != null)
{
string processfilename = oinstallfolder.ToString() + "\\SecondProgram.exe";*/
string processfilename = "SecondProgram.exe";
if (File.Exists(processfilename))
{
foreach (Process clsProcess in Process.GetProcesses())
{
if (clsProcess.ProcessName.Equals("SecondProgram"))
{
this.threadHandleOverlayBounds = new Thread(new ThreadStart(this.ExchangeMapOverlayBoundsServer));
this.threadHandleOverlayBounds.Start();
this.threadHandleOverlayFile = new Thread(new ThreadStart(this.ExchangeMapOverlayFileServer));
this.threadHandleOverlayFile.Start();
ShowWindow(clsProcess.MainWindowHandle, SW_SHOWNORMAL);
SetForegroundWindow(clsProcess.MainWindowHandle);
return;
}
}
try
{
this.threadHandleOverlayBounds = new Thread(new ThreadStart(this.ExchangeMapOverlayBoundsServer));
this.threadHandleOverlayBounds.Start();
Logger.Logger.Write("Log.txt", "Starting Filethread serverside");
this.threadHandleOverlayFile = new Thread(new ThreadStart(this.ExchangeMapOverlayFileServer));
this.threadHandleOverlayFile.Start();
Process.Start(processfilename);
}
catch { }
}
}
}
private void ExchangeMapOverlayFileServer()
{
try
{
using (NamedPipeServerStream namedPipeServer = new NamedPipeServerStream("OverlayFilePipe", PipeDirection.In, 1, PipeTransmissionMode.Byte))
{
string line;
namedPipeServer.WaitForConnection();
StreamReader sr = new StreamReader(namedPipeServer);
line = sr.ReadLine();
namedPipeServer.Disconnect();
namedPipeServer.Close();
}
}
catch(Exception e)
{
Logger.Logger.Write("OverlayGenerator.txt", "namedPipeServer exception: " + e.Message + "\n" + e.StackTrace);
}
}
this is the code from the second program
this.Closing += (s, a) =>
{
Logger.Logger.Write("Log.txt", "Window Closing");
this.threadHandleOverlayFile = new Thread(new ThreadStart(this.HandleWindowClosing));
this.threadHandleOverlayFile.Start();
while (!done)
{
Thread.Sleep(100);
}
};
private void HandleWindowClosing()
{
if (!String.IsNullOrEmpty(this.latestSavedOverlayPath))
{
try
{
using (NamedPipeClientStream namedPipeClient = new NamedPipeClientStream(".", "OverlayFilePipe", PipeDirection.Out))
{
namedPipeClient.Connect();
StreamWriter sw = new StreamWriter(namedPipeClient);
sw.AutoFlush = true;
sw.WriteLine("testphrase");
namedPipeClient.Close();
this.done = true;
}
}
catch (Exception e) { Logger.Logger.Write("OverlayGenerator.txt", "Exception in Client: " + e.Message + "\n" + e.StackTrace); }
}
else
{
Logger.Logger.Write("OverlayGenerator.txt", "Latest saved OverlayPath = " + this.latestSavedOverlayPath);
}
}
I tried this with Autoflush=true and without. With it, i get a broken pipe exception at the line
sw.WriteLine("testphrase");
and without it, the client side just runs to the end and nothing happens at all.
Where is my mistake?
Thank you.
EDIT
OK, i just don't get it. I made a test program, that is build the same way as the actual one. Two applications, the first starting the second. I start the pipe server in a thread in the first application and the client, also in a thread, in the second. The only difference is, that i run this test project in debug mode, which i can't do for the actual program. Now, in the test program, this matter is realy simple and it works. When i use the exact same approach in the actual program, it doesn't work.
In the testprogram i need to use flush on the streamwriter and i don't get an exception. The syncronization of the threads gets handled by the pipe itself, as it should be if i understood this correctly.
The Master looks like this:
private const string CLIENTPROC = "C:\\Users\\Maik\\Source\\Repos\\PipeTest\\PipeClient\\obj\\x86\\Release\\PipeClient.exe";
private ManualResetEvent threadResetEvent;
private Thread threadHandlePipeSendLast;
private Process procClient;
private string msgPipeSend;
private volatile bool bMsgPipeSend;
public MainWindow()
{
InitializeComponent();
this.threadResetEvent = new ManualResetEvent(false);
System.Diagnostics.Debug.WriteLine("### starting thread");
this.threadHandlePipeSendLast = new Thread(new ThreadStart(this.ExchangeMapOverlayFileServer));
this.threadHandlePipeSendLast.Start();
}
private void ExchangeMapOverlayFileServer()
{
System.Diagnostics.Debug.WriteLine("server thread started");
Thread.CurrentThread.Name = "apocalypse";
try
{
using (NamedPipeServerStream namedPipeServer = new NamedPipeServerStream("ClosingPipe", PipeDirection.In, 1, PipeTransmissionMode.Byte))
{
string line;
namedPipeServer.WaitForConnection();
StreamReader sr = new StreamReader(namedPipeServer);
Thread.Sleep(100);
line = sr.ReadLine();
handleRecvMsgFromPipe(line);
namedPipeServer.Disconnect();
namedPipeServer.Close();
}
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine("### " + e.Message + "\n" + e.StackTrace);
}
}
private void handleRecvMsgFromPipe(string line)
{
this.outbox.Text = line;
}
private void buttonOpenFormClient_Click(object sender, EventArgs e)
{
#if DEBUG
#else
if (this.procClient == null)
{
try
{
this.procClient = Process.Start(new ProcessStartInfo(CLIENTPROC));
}
catch (Exception exc)
{
MessageBox.Show(exc.Message, "Error");
}
}
#endif
}
}
And that's the client side:
private ManualResetEvent threadResetEvent;
private Thread threadHandlePipeSendLast;
private string msgPipeSend;
private volatile bool bMsgPipeSend;
private bool done;
public MainWindow()
{
InitializeComponent();
this.threadResetEvent = new ManualResetEvent(false);
this.Closing += (s, a) =>
{
System.Diagnostics.Debug.WriteLine("+++ FormClosing started.");
this.threadHandlePipeSendLast = new Thread(new ThreadStart(this.HandleWindowClosing));
this.threadHandlePipeSendLast.Start();
while (!done)
{
Thread.Sleep(1000);
}
};
}
private void HandleWindowClosing()
{
try
{
using (NamedPipeClientStream namedPipeClient = new NamedPipeClientStream(".", "ClosingPipe", PipeDirection.Out))
{
namedPipeClient.Connect();
StreamWriter sw = new StreamWriter(namedPipeClient);
//sw.AutoFlush = true;
sw.WriteLine("last message");
namedPipeClient.WaitForPipeDrain();
namedPipeClient.Close();
this.done = true;
}
}
catch (Exception e) { System.Diagnostics.Debug.WriteLine(e.Message + "\n" + e.StackTrace); }
}
Can anyone tell me, why this works in the test app and not in the actual one? I also tried various things from Thread.Sleep(x) at different points in the programs over Thread.Join() to pipe methods like WaitForPipeDrain (which is just ignored).

C# Progressbar & Textbox Not Updating

I've seen some great answers here and was wondering if someone could help me out.
Here's my code:
namespace expandGUIWPF
{
public static string getSHA256b64(string filepath)
{
byte[] bytes = SHA256.Create().ComputeHash(File.ReadAllBytes(filepath));
return Convert.ToBase64String(bytes);
}
private void btnRun_Click(object sender, RoutedEventArgs e)
{
{
string folder = txtFolder.Text;
string filelist = folder + "\\FileList.txt";
string[] test = Directory.GetFiles(folder, "*", System.IO.SearchOption.AllDirectories);
File.WriteAllLines(filelist, test);
int length = File.ReadLines(filelist).Count();
pBar1.Minimum = 1;
pBar1.Maximum = length;
File.WriteAllLines(filelist, test);
using (StreamReader sr = new StreamReader(filelist))
{
string line;
while ((line = sr.ReadLine()) != null)
{
string oldfile = line;
string newfile = oldfile + ".expanded";
string oldhash = "";
string newhash = "";
try
{
ProcessStartInfo startInfo = new ProcessStartInfo(#"C:\test\test.exe", oldfile + " " + newfile);
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(startInfo);
Thread.Sleep(1000);
if (File.Exists(oldfile))
{
oldhash = getSHA256b64(oldfile);
}
if (File.Exists(newfile))
{
newhash = getSHA256b64(newfile);
File.Delete(oldfile);
File.Move(newfile, oldfile);
}
pBar1.Value = pBar1.Value + 1;
txtLog.AppendText(oldfile + "\r\n Old: " + oldhash + "\r\n New: " + newhash + "\r\n");
if (!(oldhash == newhash))
{
txtLog.AppendText("Successfully expanded file \r\n");
}
else
{
txtLog.AppendText("Unable to expand file \r\n");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
}
}
}
}
The problem is that my progressbar isn't updating. I know a little C# but I'm a beginner to WPF and can't get my head around setting up a background worker to update my UI. Would someone be able to give me a few pointers please? Currently the app works fine, but the progressbar jumps to 100% finished and all of the text suddenly appears.
Thanks in advance!
Tom
First you'll want your background worker to handle the processes in its DoWork event. Within that event you can call the ProgressChanged event to update the progress bar. Below is an example:
private void btnRun_Click(object sender, RoutedEventArgs e)
{
if(workerThread.IsBusy == false) // Make sure someone doesn't click run multiple times by mistake
{
pBar1.Value = 0;
workerThread.RunWorkerAsync();
}
}
private void workerThread_DoWork(object sender, DoWorkEventArgs e)
{
// Definitions and so forth
pBar1.Minimum = 0;
pBar1.Maximum = length;
int status = 0;
using (StreamReader sr = new StreamReader(filelist))
{
string line;
while ((line = sr.ReadLine()) != null)
{
// Try/Catch work here
status++;
workerThread.ReportProgress(status);
}
}
private void workerThread_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pBar1.Value = e.ProgressPercentage;
}
Just understand that the thread that's running the Form is the same one that will be used to update the form's controls. So if you have 'stuff' to do - like encrypting / decrypting lines from a file - you need to perform those items on another thread with a callback, otherwise the form display wont update until it's done with your stuff to do. You can raise events from inside a worker thread -- and catch them using an event handler on the main (form) thread to update the progress bar.
It seems that your UI thread is being blocked, in windows forms programming you have one message pump and while you main thread (UI) is doing something else it has to wait before it can process messages. You can fix this problem by setting up a background worker to send updates
For more information on UI thread and the message pump see this
http://www.codeproject.com/Articles/10311/What-s-up-with-BeginInvoke
For infomation on the backgroundworker thread see this
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Small Example code
// This event handler is where the time-consuming work is done.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500);
worker.ReportProgress(i * 10);
}
}
}

Background Worker Locking Main Thread - Windows Forms C#

I have a background worker that I use to create files in the background.
I had it working so that the files were created and the UI was still responsive.
I made some changes and now I can't figure out why the background worker is locking my main thread.
Here are my background worker methods. I don't have a progress changed event.
private void filecreator_bgw_DoWork(object sender, DoWorkEventArgs e)
{
if (filecreator_bgw.CancellationPending == true)
{
e.Cancel = true;
}
else
{
myManager.createFiles((SelectedFileTypes) e.Argument);
}
}
private void filecreator_bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == true)
{
//status_label.Text = "Canceled!";
}
else if (e.Error != null)
{
//status_label.Text = "Error: " + e.Error.Message;
}
else
{
// Check the file manager object to see if the files were created successfully
status_label.Text = "COMPLETE";
file_statusLabel.Text = "Files Created: " + DateTime.Now.ToShortTimeString();
System.Threading.Thread.Sleep(5000);
status_label.Text = "Click Create Files to Begin";
createfiles_button.Enabled = true;
}
}
Here is the method to create the files.
public void createFiles(SelectedFileTypes x)
{
if (string.IsNullOrEmpty(Filename) || (x.isCSV == false && x.isTAB == false && x.isXML == false))
{
filesCreated = false;
return;
}
// Declare the streams and xml objects used to write to the output files
XDocument xmlFile;
StreamWriter swCSV;
StreamWriter swTAB;
CSVFilename = Path.GetDirectoryName(Filename) + Path.DirectorySeparatorChar.ToString() +
Path.GetFileNameWithoutExtension(Filename) + "CSV_TEST.csv";
swCSV = new StreamWriter(CSVFilename);
TABFilename = Path.GetDirectoryName(Filename) + Path.DirectorySeparatorChar.ToString() +
Path.GetFileNameWithoutExtension(Filename) + "TAB_TEST.csv";
swTAB = new StreamWriter(TABFilename);
XMLFilename = Path.GetDirectoryName(Filename) + Path.DirectorySeparatorChar.ToString() +
Path.GetFileNameWithoutExtension(Filename) + "XML_TEST.csv";
xmlFile = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XComment("Crosswalk"));
xmlFile.Add(new XElement("ACCOUNTS"));
// String array for use when creating xml nodes
string[] splits;
// String used to read in a line from the input file
string line = "";
// Use a try and catch block, if any errors are caught, return false
try
{
// Read each line in the file and write to the output files
using (StreamReader sr = new StreamReader(Filename))
{
int i = 0;
while ((line = sr.ReadLine()) != null)
{
if (x.isCSV)
{
swCSV.WriteLine(line.Replace(delim, ","));
}
if (x.isTAB)
{
swTAB.WriteLine(line.Replace(delim, "\t"));
}
if (x.isXML)
{
if (i <= 0)
{
i++;
continue;
}
splits = line.Split(new string[] { delim }, StringSplitOptions.RemoveEmptyEntries);
xmlFile.Root.Add(
new XElement("ACCOUNTS",
from s in header
select new XElement(s, splits[Array.IndexOf(header, header.Where(z => z.Equals(s, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault())])
)
);
}
}
// Dispose of all objects
swCSV.Close();
swCSV.Dispose();
swTAB.Close();
swTAB.Dispose();
if (x.isXML)
{
//xmlFile.Save(Path.GetFullPath(Filename) + Path.GetFileNameWithoutExtension(Filename) + "_TEST.xml");
xmlFile.Save(XMLFilename);
}
}
}
catch (Exception)
{
filesCreated = false;
return;
}
// Return true if file creation was successfull
filesCreated = true;
}
In the do work method, I build a simple struct to determine what output file types should be made and then I pass it to the method. If I comment out that call to create the files, the UI still does not respond.
In the create files method, I build out the files based on the input file that I am transforming. I do use a LINQ statement to help build out XML tags, but the arrays holding the tags values are small, 3-5 elements depending on the file chosen.
Is there a simple solution, or should I re-design the method. If I have to re-design, what are things I should keep in mind to avoid locking the main thread.
Thanks
Here is how I call the runworkerasync method:
private void createfiles_button_Click(object sender, EventArgs e)
{
SelectedFileTypes selVal = new SelectedFileTypes();
foreach (var structVal in outputformats_checkedListBox.CheckedItems)
{
if (structVal.ToString().Equals("CSV", StringComparison.InvariantCultureIgnoreCase))
selVal.isCSV = true;
if (structVal.ToString().Equals("TAB", StringComparison.InvariantCultureIgnoreCase))
selVal.isTAB = true;
if (structVal.ToString().Equals("XML", StringComparison.InvariantCultureIgnoreCase))
selVal.isXML = true;
}
// Call the FileManager object's create files method
createfiles_button.Enabled = false;
filecreator_bgw.RunWorkerAsync(selVal);
}
UPDATE:
I updated the call to start the worker and then the call to create the files using the argument passed into the worker.
You cannot interact with most UI controls directly from a BackgroundWorker. You need to access outputformats_checkedListBox.CheckedItems from the UI thread and pass the resulting SelectedFileTypes object into the BackgroundWorker as a parameter.
Also, pleas enote that your cancellation logic really didn't do much. In order for it to work well, you need to check CancellationPending throughout the process, not just when starting.
Here is a rough example of how you should start the worker:
private void StartWorker()
{
SelectedFileTypes selVal = new SelectedFileTypes();
foreach (var structVal in outputformats_checkedListBox.CheckedItems)
{
if (structVal.ToString().Equals("CSV", StringComparison.InvariantCultureIgnoreCase))
selVal.isCSV = true;
if (structVal.ToString().Equals("TAB", StringComparison.InvariantCultureIgnoreCase))
selVal.isTAB = true;
if (structVal.ToString().Equals("XML", StringComparison.InvariantCultureIgnoreCase))
selVal.isXML = true;
}
filecreator_bgw.RunWorkerAsync(selVal);
}
private void filecreator_bgw_DoWork(object sender, DoWorkEventArgs e)
{
SelectedFileTypes selVal = (SelectedFileTypes)e.Argument;
myManager.createFiles(selVal);
}

My C# multi-threaded console application seems to not be multithreaded

Here's my C# console program that uses Powerpoint to convert ppt files to folders of pngs. This is supposed to be an automated process that runs on its own server.
I expect that as soon as a thread creates an image from a file, it should immediately remove the images and the source file.
The actual behavior is that, if five threads are running, it'll wait for five folders of images to be created before any thread can move any files. I'm able to see the images being created, and compare that with the Console readout, so I can see that a thread isn't trying to move the file.
Only after all the other threads have made their images, will any thread try to move the files. I suspect this is wrong.
This is an Amazon EC2 Medium instance, and it appears to max out the CPU, so five threads might be too much for this.
I also find that I can hardly use Windows Explorer while this program is running.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.IO;
using Microsoft.Office.Core;
using PowerPoint = Microsoft.Office.Interop.PowerPoint;
using System.Diagnostics;
using System.Timers;
namespace converter
{
class Program
{
public static int threadLimit=0;
public static int currThreads = 0;
static void Main(string[] args)
{
var inDir = args[0];
var outDir = args[1]+"\\";
var procDir = args[2]+"\\";
Int32.TryParse(args[3],out threadLimit);
Thread[] converterThreads = new Thread[threadLimit];
while (true)
{
System.Threading.Thread.Sleep(1000);
var filePaths = Directory.GetFiles(inDir, "*.*", SearchOption.AllDirectories).Where(s => s.EndsWith(".pptx") && !s.Contains("~$") || s.EndsWith(".ppt") && !s.Contains("~$"));
var arrPaths = filePaths.ToArray();
for(var i=0; i< arrPaths.Length; i++)
{
if (currThreads < threadLimit && currThreads < arrPaths.Length)
{
Console.WriteLine("currThreads= " + currThreads + " paths found= " + arrPaths.Length);
try
{
var fileNameWithoutExtension = arrPaths[currThreads].Replace(inDir, "").Replace(".pptx", "").Replace(".ppt", "").Replace("\\", "");
var filenameWithExtension = arrPaths[currThreads].Substring(arrPaths[currThreads].LastIndexOf("\\") + 1);
var dir = arrPaths[currThreads].Replace(".pptx", "").Replace(".ppt", "");
Conversion con = new Conversion(arrPaths[currThreads], dir, outDir, procDir, filenameWithExtension, fileNameWithoutExtension);
converterThreads[i] = new Thread(new ThreadStart(con.convertPpt));
converterThreads[i].Start();
Console.WriteLine(converterThreads[i].ManagedThreadId + " is converting " + fileNameWithoutExtension);
}
catch (Exception e)
{
Console.WriteLine(string.Format("Unable to convert {0} ", arrPaths[i]) + e);
}
}
}
for (var i = 0; i < converterThreads.Length; i++)
{
if (converterThreads[i] != null)
{
if (!converterThreads[i].IsAlive)
{
converterThreads[i].Abort();
converterThreads[i].Join(1);
Console.WriteLine("thread " + converterThreads[i].ManagedThreadId + " finished, "+currThreads+" remaining");
converterThreads[i] = null;
}
}
}
if (currThreads == 0)
{
try
{
foreach (Process proc in Process.GetProcessesByName("POWERPNT"))
{
proc.Kill();
}
}
catch (Exception e3)
{
}
}
}
}
}
class Logger{
static void toLog(String msg)
{
//TODO: log file
}
}
class Conversion{
static int numberOfThreads=0;
String input;
String output;
String outDir;
String process;
String nameWith;
String nameWithout;
int elapsedTime;
System.Timers.Timer time;
public Conversion(String input, String output, String outDir, String processDir, String nameWith, String nameWithout)
{
this.input = input;
this.output = output;
this.outDir = outDir;
process = processDir;
this.nameWith = nameWith;
this.nameWithout = nameWithout;
numberOfThreads++;
Console.WriteLine("number of threads running: " + numberOfThreads);
Program.currThreads = numberOfThreads;
time = new System.Timers.Timer(1000);
time.Start();
time.Elapsed += new ElapsedEventHandler(OnTimedEvent);
elapsedTime = 0;
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
elapsedTime++;
}
public void convertPpt()
{
var app = new PowerPoint.Application();
var pres = app.Presentations;
try
{
var file = pres.Open(input, MsoTriState.msoFalse, MsoTriState.msoFalse, MsoTriState.msoFalse);
file.SaveAs(output, Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType.ppSaveAsPNG, MsoTriState.msoTrue);
file.Close();
app.Quit();
Console.WriteLine("file converted " + input);
}
catch (Exception e)
{
Console.WriteLine("convertPpt failed");
}
moveFile();
moveDir();
}
public void moveFile()
{
Console.WriteLine("moving" + input);
try
{
System.Threading.Thread.Sleep(500);
Console.WriteLine(string.Format("moving {0} to {1}", input, process + nameWith));
if (File.Exists(process + nameWith))
{
File.Replace(input, process + nameWith, null);
}
else
{
File.Move(input, process + nameWith);
}
}
catch (Exception e)
{
Console.WriteLine(string.Format("Unable to move the file {0} ", input) + e);
try
{
foreach (Process proc in Process.GetProcessesByName("POWERPNT"))
{
proc.Kill();
}
}
catch (Exception e3)
{
}
}
}
public void moveDir()
{
Console.WriteLine("moving dir " + output);
try
{
System.Threading.Thread.Sleep(500);
Console.WriteLine(string.Format("moving dir {0} to {1} ", output, outDir + nameWithout));
if (Directory.Exists(outDir + nameWithout))
{
Directory.Delete(outDir + nameWithout, true);
}
if (Directory.Exists(output))
{
Directory.Move(output, outDir + nameWithout);
}
}
catch (Exception e)
{
Console.WriteLine(string.Format("Unable to move the directory {0} ", output) + e);
try
{
foreach (Process proc in Process.GetProcessesByName("POWERPNT"))
{
proc.Kill();
}
}
catch (Exception e3)
{
}
}
finally
{
numberOfThreads--;
Program.currThreads = numberOfThreads;
Console.WriteLine("took " + elapsedTime + "seconds");
}
}
}
}
Every 1000ms you get a list of files in inDir and potentially start a thread to process each file. You have very complex logic surrounding whether or not to start a new thread, and how to manage the lifetime of the thread.
The logic is too complex for me to spot the error without debugging the code. However, I would propose an alternative.
Have a single thread watch for new files and place the file path into a BlockingCollection of files for processing. That thread does nothing else.
Have N additional threads that retrieve file paths from the BlockingCollection and process them.
This is known as a Producer / Consumer pattern and is ideal for what you are doing.
The example code at the bottom of the linked MSDN page shows an implementation example.
On a side note, you are catching and swallowing Exception e3. Don't catch something you will not handle, it hides problems.

Categories