I have a small program that starts as an appbar (a window which docks to the desktop and can be hidden (on the top, right, bottom, or left side of the desktop when not in use). The program permits a user to drag a file (or files) from any given location on to the appbar and convert it to PDF (by converting each file to PDF and then merging the resulting PDF's into a single PDF file).
The conversion process runs on a seperate thread using a backgroundworker. Once the backgroundworker acquires the list of files, I have a modal dialog which pops up, having loaded a listview with the relevant files, and allows the user to reorder them prior to the final merge process.
I am having cross-thread issues with the modal dialog. I have searched high and low for a solution, but am stumped. The problems arises as a result of the use of the keyword this.
I have tried the following code in the midst of the backgroundworker:
using (var md = new MergeDlg())
{
md.Files = (string[])files;
if (md.ShowDialog(this) == DialogResult.OK)
files = md.Files;
}
If I remove the keyword this I get no error, but the dialog behaves as if it is started on the main thread and the backgroundworkerthread continues as if there is no modal dialog - I understand that is because the dialog is started on the main UI thread.
I have also tried moving the dialog creation out of the background worker thread and calling it in the thread
the code to create the modal dialog is as follows:
private string[] ShowMergeDlg(string[] files)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new Action(() =>
{
MergeDlg md = new MergeDlg();
md.Files = (string[])files;
if (md.ShowDialog(this) == DialogResult.OK)
files = md.Files;
}
));
}
else
{
MergeDlg md = new MergeDlg();
md.Files = (string[])files;
if (md.ShowDialog(this) == DialogResult.OK)
files = md.Files;
}
return files;
}
On the backgroundworker thread, the function is called:
files = ShowMergeDlg(files);
Again that code obviously starts the dialog on the main UI thread with the same result.
My question is then:
How do I show a modal dialog on a backgroundworker thread, pausing execution of the thread until such times as the modal dialog has been closed?
You better switch to async/await and Tasks. Here a very limited sample
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click( object sender, EventArgs e )
{
button1.Enabled = false;
label1.Text = "acquire files ...";
ICollection<string> acquiredFiles = await AcquireFileAsync();
label1.Text = "select files ...";
ICollection<string> selectedFiles = SelectFilesDialog( acquiredFiles );
label1.Text = "process files ...";
await ProcessFilesAsync( selectedFiles );
label1.Text = "finished.";
button1.Enabled = true;
}
private async Task ProcessFilesAsync( ICollection<string> selectedFiles )
{
foreach (var item in selectedFiles)
{
await Task.Delay( 250 ).ConfigureAwait( false );
}
}
private ICollection<string> SelectFilesDialog( ICollection<string> acquiredFiles )
{
var dialog = new Form2();
dialog.ShowDialog();
return acquiredFiles;
}
private async Task<ICollection<string>> AcquireFileAsync()
{
await Task.Delay( 2500 ).ConfigureAwait( false );
return Enumerable.Range( 1, 20 ).Select( e => e.ToString() ).ToList();
}
}
If you call ShowMergeDlg from the background worker, InvokeRequired will be true, then the dialog creates on the UI thread. So just remove it, let the dialog create on the background thread.
private string[] ShowMergeDlg(string[] files)
{
/*
if (this.InvokeRequired)
{
this.BeginInvoke(new Action(() =>
{
MergeDlg md = new MergeDlg();
md.Files = (string[])files;
if (md.ShowDialog(this) == DialogResult.OK)
files = md.Files;
}
));
}
else
*/
{
MergeDlg md = new MergeDlg();
md.Files = (string[])files;
if (md.ShowDialog(this) == DialogResult.OK)
files = md.Files;
}
return files;
}
My test code
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 5; ++i)
{
this.Invoke(new Action(() => textBox1.AppendText("1")));
Thread.Sleep(500);
}
var f2 = new Form2();
if(f2.ShowDialog(this) == DialogResult.OK)
this.Invoke(new Action(() => textBox1.AppendText("2")));
else
this.Invoke(new Action(() => textBox1.AppendText("3")));
for (int i = 0; i < 100; ++i)
{
this.Invoke(new Action(() => textBox1.AppendText("1")));
Thread.Sleep(500);
}
}
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
}
Related
I'm trying to save a rather large text file when the user hits the save button. It can be up to 30MBs. After pressing the button, I'd like the texbox to display "Saving..." as it's saving the file and when it completes, display "Saved". However I can't get this to work. I've tried using Task.run, await task.Run, and using a background worker. All these options hang the UI until the save completes. The textbox does not display "Saving..." until after it saves and the program is unresponsive until then. How can I fix this?
private async void btnSave_Click(object sender, RoutedEventArgs e)
{
SaveFileDialog saveFileDialog1 = new SaveFileDialog();
saveFileDialog1.ShowDialog();
// If the file name is not an empty string open it for saving.
if (saveFileDialog1.FileName != "")
{
logFileName = saveFileDialog1.FileName;
btnOpenFile.IsEnabled = false;
btnSave.IsEnabled = false;
tbText1.Text += "\n\n***Saving...***\n";
tbText1.ScrollToEnd();
await Task.Run(() => File.WriteAllText(logFileName, Results.ToString()));
tbText1.Text += "\n\n***SAVED***\n\n";
tbText1.ScrollToEnd();
btnOpenFile.IsEnabled = true;
btnSave.IsEnabled = true;
}
As discussed in the comments, the problem is with Results.ToString().
I tried to reproduce the issue with this code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
for (int i = 1; i < 40536; i++)
{
stringBuilder.Append(new string('a', i));
}
}
readonly StringBuilder stringBuilder = new StringBuilder();
int tickNumber = 0;
private void sync_Click(object sender, EventArgs e)
{
button1.Enabled = false;
stringBuilder.ToString();
button1.Enabled = true;
}
private async void async_Click(object sender, EventArgs e)
{
button2.Enabled = false;
await Task.Run(() => stringBuilder.ToString());
button2.Enabled = true;
}
private void timer1_Tick(object sender, EventArgs e)
{
tickNumber %= 50;
tickNumber++;
label1.Text = new string('.', tickNumber);
}
}
But it works as expected:
Sometimes UI hands for a little bit though. Is this what are you talking about?
Try moving code that generates contents for StringBuilder inside the task (so this StringBuilder only exists in background thread)
Hi I have read other tutorials but could not figure it out. I am running a task and when task is completed I want to hide the current form and load another form but It hangs and nothing is displayed. This is my code please guide me -
public Loading()
{
InitializeComponent();
Shown += Loading_Shown;
}
private void Loading_Shown(object sender, EventArgs e)
{
label2.Text = "Step 1...";
Task.Run(() =>
{
if (Directory.Exists(contentPath))
{
filePresent = false;
}
if (filesPresent == false)
{
BeginInvoke(
(MethodInvoker)delegate
{
label2.Text = "Downloading Files...";
}
);
Directory.CreateDirectory(contentPath);
Home form = new Home();
form.Visible = true;
}
else
{
Home form = new Home();
form.Visible = true;
}
});
}
The other form loads half and screen hangs. Please guide me how to continue with this. Thanks
You don't create the second form "when [the] task is completed", but inside that task. So you create the second form on a different thread than the first one. This is a bad idea.
One solution is to make Loading_Shown an async method and await the task. Then, when the task really has completed and control flow returned to the original ui thread, you can create the second form:
private async void Loading_Shown(object sender, EventArgs e)
{
label2.Text = "Step 1...";
await Task.Run(() =>
{
// different thread
filePresent = Directory.Exists(contentPath);
if (!filePresent) Directory.CreateDirectory(contentPath);
});
// back on UI thread
if (!filesPresent)
{
label2.Text = "Downloading Files..."; });
Home form = new Home();
form.Visible = true;
}
else{
Home form = new Home();
form.Visible = true;
}
}
I have created a Windows Forms program that breaks down a file and sends its components to a server. These files are large, so I created a progressBar so users don't think it froze while the transactions are happening. What I would like to do is have some mechanism that will actively trigger only when all threads are complete without blocking the UI thread (again, so the plebs wont think its frozen). The best I could come up with is a kind of passive "wait until true" but I feel like there has to be a better way to do this. I have experimented with trying to create an event or a callback but honestly I've just ended up more confused than when I started. Here is an example of how I am doing this now:
public partial class Program : Form
{
private readonly OpenFileDialog _ofd = new OpenFileDialog();
public delegate void BarDelegate();
private string _path;
private void button1_Click(object sender, EventArgs e)
{
if (_ofd.ShowDialog() != DialogResult.OK) return;
textBox1.Text = _ofd.SafeFileName;
_path = _ofd.FileName;
}
private void button2_Click(object sender, EventArgs e)
{
var allLinesFromFile = File.ReadAllLines(_path);
progressBar1.Minimum = 0;
progressBar1.Maximum = allLinesFromFile.Length;
Task.Factory.StartNew(() => Parallel.ForEach(allLinesFromFile, DoSomething));
while (progressBar1.Value < progressBar1.Maximum) //there has to be a better way to do this...
{
MessageBox.Show("Please wait.", "Record Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
}
//some processes here which should only take place after all threads are complete.
var postingComplete = MessageBox.Show("The posting is complete!", "Record Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
if (postingComplete == DialogResult.OK) Environment.Exit(0);
}
private void DoSomething(string record)
{
//some string manipulation and server transactions here
BeginInvoke(new BarDelegate(() => progressBar1.Increment(1)));
}
}
Try using Microsoft's Reactive Framework (NuGet "System.Reactive.Windows.Forms") for this. Then your code becomes:
private void button2_Click(object sender, EventArgs e)
{
var allLinesFromFile = File.ReadAllLines(_path);
progressBar1.Minimum = 0;
progressBar1.Maximum = allLinesFromFile.Length;
IDisposable subscription =
allLinesFromFile
.ToObservable()
.SelectMany(f => Observable.Start(() => DoSomething(f)))
.ObserveOn(this)
.Do(x => progressBar1.Value += 1)
.Subscribe(x => { }, () =>
{
var postingComplete = MessageBox.Show("The posting is complete!", "Record Poster", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
if (postingComplete == DialogResult.OK)
{
Application.Exit();
}
});
}
private void DoSomething(string record)
{
System.Threading.Thread.Sleep(5);
}
If you need to stop this early then just call subscription.Dispose(). I've tested this and it works fine for me.
You should be using the BackGroundWorker class, see: How to use a BackgroundWorker?
And use BackGroundWorker.RunWorkerComplete for when the thread has finished
Background worker:
**Backgroundworker (System.ComponentModel)**
BackgroundWorker loader = new BackgroundWorker();
loader.DoWork += load_Specials_BW_Thread;
loader.WorkerReportsProgress = true;
loader.ProgressChanged += load_Special_Feeds_Progress_Changed;
private void load_Specials_BW_Thread(object sender, DoWorkEventArgs e)
{
int pctComplete = (int)Math.Floor(ptComplete * 100);//recs done / total recs
(sender as BackgroundWorker).ReportProgress(pctComplete);
}
Good luck!
In my application i am checking my files by open Wireshark process before add to my Listbox.
this is Add Directory click event who take root folder and checking all this files inside this folder and sub folders:
private void btnAddDir_Click(object sender, EventArgs e)
{
try
{
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
{
ThreadStart threadStart = delegate
{
foreach (string file in SafeFileEnumerator.EnumerateFiles(folderBrowserDialog1.SelectedPath, "*.*", SearchOption.AllDirectories))
{
Interlocked.Increment(ref numWorkers);
StartBackgroundFileChecker(file);
}
};
Thread thread = new Thread(threadStart);
thread.IsBackground = true;
thread.Start();
}
}
catch (Exception)
{ }
}
private void StartBackgroundFileChecker(string file)
{
ListboxFile listboxFile = new ListboxFile();
listboxFile.OnFileAddEvent += listboxFile_OnFileAddEvent;
BackgroundWorker backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork +=
(s3, e3) =>
{
//check my file
};
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
backgroundWorker.RunWorkerAsync();
}
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (Interlocked.Decrement(ref numWorkers) == 0)
{
//update my UI
}
}
When i am checking this file i am open Wireshark process so if i choose folder with many files, many Wireshark processes opned and this take a lot on memory,
how can i wait until my BackgroundWorker finish and only then open new one ?
As I understand you want only single background worker launched at time. If so, then try this (based on System.Threading.AutoResetEvent)
//introduce additional field
private AutoResetEvent _workerCompleted = new AutoResetEvent(false);
//modify StartBackgroundFileChecker
private void StartBackgroundFileChecker(string file)
{
ListboxFile listboxFile = new ListboxFile();
listboxFile.OnFileAddEvent += listboxFile_OnFileAddEvent;
BackgroundWorker backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork +=
(s3, e3) =>
{
//check my file
};
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
backgroundWorker.RunWorkerAsync();
//new code - wait for completion
_workerCompleted.WaitOne();
}
//add completion notification to backgroundWorker_RunWorkerCompleted
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (Interlocked.Decrement(ref numWorkers) == 0)
{
//update my UI
}
//new code - notify about completion
_workerCompleted.Set();
}
In that solution your background thread will start new BackgroundWorker one by one - this can be not optimal (you could avoid BackgroundWorker at all and simply update UI via Dispatch in threadStart delegate)
In my opinion better to control number of parallel threads and still process files in multiple but limited numbers of threads.
Here is the alternative solution (based on System.Threading.Tasks namespace):
private void btnAddDir_Click(object sender, EventArgs e)
{
var selectedPath = folderBrowserDialog1.SelectedPath;
Task.Factory.StartNew(() =>
{
var files = Directory.EnumerateFiles(selectedPath, "*.*", SearchOption.AllDirectories);
Parallel.ForEach(files,
new ParallelOptions
{
MaxDegreeOfParallelism = 10 // limit number of parallel threads here
},
file =>
{
//process file here - launch your process
});
}).ContinueWith(
t => { /* when all files processed. Update your UI here */ }
,TaskScheduler.FromCurrentSynchronizationContext() // to ContinueWith (update UI) from UI thread
);
}
You can tweak this solution for your specific needs.
Used classes/methods (see MSDN for reference):
Task
TaskScheduler.FromCurrentSynchronizationContext
Parallel.ForEach Method (IEnumerable, ParallelOptions, Action)
Maybe something like this, instead of the foreach keep a List of the files and after complete just take the first element and update your List
private List<string> _files;
private void btnAddDir_Click(object sender, EventArgs e)
{
try
{
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
{
_files = new List<string>(SafeFileEnumerator.EnumerateFiles(folderBrowserDialog1.SelectedPath, "*.*", SearchOption.AllDirectories));
Interlocked.Increment(ref numWorkers);
var file = _files.FirstOrDefault();
if(file != null)
StartBackgroundFileChecker(file);
}
}
catch (Exception)
{ }
}
private void StartBackgroundFileChecker(string file)
{
ListboxFile listboxFile = new ListboxFile();
listboxFile.OnFileAddEvent += listboxFile_OnFileAddEvent;
BackgroundWorker backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork +=
(s3, e3) =>
{
//check my file
};
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
backgroundWorker.RunWorkerAsync();
}
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (Interlocked.Decrement(ref numWorkers) == 0)
{
//update my UI
_files = _files.Skip(1);
var file = _files.FirstOrDefault();
if(file != null)
StartBackgroundFileChecker(file);
}
}
my application contain Listbox that i am add files into and when i do it with Add folder it check all the files inside this folder and do it by open different thread on each file and then add those files into the Listbox - so should i use lock to prevent case that 2 (or more) files will try to add file at same time ?
private void btnAddDir_Click_1(object sender, EventArgs e)
{
int totalCount = 0;
int count = 0;
string fileToAdd = string.Empty;
List<string> filesList = new List<string>();
BackgroundWorker backgroundWorker = null;
DialogResult dialog = folderBrowserDialog1.ShowDialog();
if (dialog == DialogResult.OK)
{
btnAddfiles.Enabled = false;
btnAddDir.Enabled = false;
btnPlay.Enabled = false;
Editcap editcap = new Editcap();
foreach (string file in SafeFileEnumerator.EnumerateFiles(folderBrowserDialog1.SelectedPath, "*.*", SearchOption.AllDirectories))
{
if (editcap.isWiresharkFormat(file))
{
filesList.Add(file);
totalCount++;
}
}
backgroundWorker = new BackgroundWorker();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork +=
(s1, e1) =>
{
foreach (string fileName in filesList)
{
if (editcap.isWiresharkFormat(fileName))
{
if (editcap.isLibpcapFormat(fileName))
{
backgroundWorker.ReportProgress(0, fileName);
count++;
}
else if (!editcap.isLibpcapFormat(fileName))
{
fileToAdd = editcap.getNewFileName(fileName);
if (new FileInfo(fileToAdd).Exists)
{
backgroundWorker.ReportProgress(0, fileToAdd);
count++;
}
}
this.Invoke((MethodInvoker)delegate
{
labelStatus.Text = string.Format("Please wait..({0}/{1} files were added)", count.ToString("#,##0"), totalCount.ToString("#,##0"));
if(listBoxFiles.Items.Count != 0)
listBoxFiles.SetSelected(listBoxFiles.Items.Count - 1, true);
});
}
}
};
backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
(s1, e1) =>
{
});
backgroundWorker.ProgressChanged +=
(s1, arguments) =>
{
listBoxFiles.Items.Add(arguments.UserState);
};
backgroundWorker.RunWorkerAsync();
}
}
IIRC (No C# for long time for me) :)
The completed event of the background worker is raised on the UI thread.
So basically, different completion events will be ran sequential - and no need for lock.
See BackgroundWorker RunWorkerCompleted Event