I need to modify the GUI from inside of a method that takes long time to finish. As I read other posts,
one of the solution is to use Control.Dispatcher.BeginInvoke to set the GUI inside the worker thread.
However, I don't have a clue how to do this here.
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew( () =>
{
ProcessFilesThree();
});
}
private void ProcessFilesThree()
{
string[] files = Directory.GetFiles(#"C:\temp\In", "*.jpg", SearchOption.AllDirectories);
Parallel.ForEach(files, (currentFile) =>
{
string filename = Path.GetFileName(currentFile);
// the following assignment is illegal
this.Text = string.Format("Processing {0} on thread {1}", filename,
Thread.CurrentThread.ManagedThreadId);
});
this.Text = "All done!"; // <- this assignment is illegal
}
}
Try the following:
msg = string.Format("Processing {0} on thread {1}", filename,
Thread.CurrentThread.ManagedThreadId);
this.BeginInvoke( (Action) delegate ()
{
this.Text = msg;
});
private void ProcessFilesThree()
{
// Assuming you have a textbox control named testTestBox
// and you wanted to update it on each loop iteration
// with someMessage
string someMessage = String.Empty;
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000); //Simulate a second of work.
someMessage = String.Format("On loop iteration {0}", i);
testTextBox.Dispatcher.BeginInvoke(new Action<string>((message) =>
{
testTextBox.Text = message;
}), someMessage);
}
}
Related
I try to call a function after thread finished but I can't .
I only can use while(threadName.isAlive) method before my function caller code , but it's not good because the program stops when i use this code . have you any idea ?
public partial class Form1 : Form
{
Thread myThread;
string myString = string.Empty;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
myThread = new Thread(write);
myThread.Start();
while (myThread.IsAlive) ;
textBox1.Text = myString;
}
public void write()
{
for (int i = 0; i < 10; i++) {
myString += "aaa " + i + "\r\n";
Thread.Sleep(1000);
}
}
}
If you must attach to a Thread rather than a Task then you can just start a task to wait for the thread to exit and then run some additional code, like this:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
static class Program
{
static void Main()
{
Thread thread = new Thread(work);
thread.Start();
Task.Run(() =>
{
thread.Join();
Console.WriteLine("Run after thread finished");
});
Console.ReadLine();
}
static void work()
{
Console.WriteLine("Starting work");
Thread.Sleep(1000);
Console.WriteLine("Finished work");
}
}
}
However, the modern way to approach this is to use Task, await and async.
For example:
async void button1_Click(object sender, EventArgs e)
{
textBox1.Text = "Awaiting task";
await writeAsync();
textBox1.Text = "Task finished";
}
Task writeAsync()
{
return Task.Run(() => write());
}
void write()
{
Thread.Sleep(10000);
}
If you try this second approach, you'll see that the UI remains responsive while the textbox says "Awaiting task".
Also note that normally you'd want to stop the user from being able to press the button again while the task is being awaited, to avoid multiple tasks being run. The easiest way to do that is to disable the button while the task is active like so:
async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
textBox1.Text = "Awaiting task";
await writeAsync();
textBox1.Text = "Task finished";
button1.Enabled = true;
}
Switch to Task from Thread and let .Net do the (low level) work for you:
public async Task<string> write() {
string myString = string.Empty;
for (int i = 0; i < 10; i++) {
myString += "aaa " + i + "\r\n";
await Task.Delay(1000);
}
return myString;
}
private async void button1_Click(object sender, EventArgs e) {
string result = await write();
// continue with (please, notice await) with assigning
textBox1.Text = result;
}
I have a Form with two buttons (Start , Stop).
When I press Start Button a Task is initialized and a function is called that keeps running until Stop button is pressed.
But When I press Stop button Form freezes. why?
I have copied snippet from StackOverflow but it still freezes Form.
So tell me how to Cancel Task properly?
public partial class Form1 : Form
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private Task _task;
public Form1()
{
InitializeComponent();
}
//Funtion that runs when Task is initialized.
private void EventLoop(CancellationToken token)
{
//Code here..
while (!token.IsCancellationRequested)
{
//Code here..
}
if (token.IsCancellationRequested)
{
MessageBox.Show("Operation Complete..!!");
}
}
//Function for Start Button.
private void Start_Click(object sender, EventArgs e)
{
_task = Task.Factory.StartNew(() => EventLoop(_cts.Token), _cts.Token);
}
//Function for Stop button.
private void Stop_Click(object sender, EventArgs e)
{
_cts.Cancel();
}
}
Similar Example from MSDN:
var compute = Task.Factory.StartNew(() =>
{
return SumRootN(j);
}, tokenSource.Token);`
Form After Stop button is pressed.
token.IsCancellationRequested is true .
Full EventLoop() Function.
private void EventLoop(CancellationToken token)
{
SerialPort sp = new SerialPort();
string text, batch, part, courseName;
text = batch = part = courseName = "";
int courseId = 0;
this.Invoke((MethodInvoker)delegate()
{
text = portCB.SelectedItem.ToString();
batch = comboBox2.SelectedItem.ToString();
part = comboBox3.SelectedItem.ToString();
courseName = comboBox1.SelectedItem.ToString();
progressBar1.Value = 20;
using (Model1 db = new Model1())
{
courseId = db.Courses.Where(c => c.Course_name.ToUpper() == courseName.ToUpper()).Select(c => c.Course_Id).Single();
}
});
sp.PortName = text;
sp.BaudRate = 9600;
sp.Open();
while (!token.IsCancellationRequested)
{
text = sp.ReadLine();
if (text.Contains("Found ID #"))
{
this.Invoke((MethodInvoker)delegate()
{
textBox2.Clear();
textBox2.Text = "Getting Registation ID.\n";
progressBar1.Value = 60;
});
string splitText = text.Split('#')[1];
int end = splitText.IndexOf(' ');
int id = int.Parse(splitText.Substring(0, end));
using (Model1 db = new Model1())
{
var result = db.Students.Where(s => s.Reg_Id == id && s.Batch == batch && s.Class == part).Select(s => s).SingleOrDefault();
if (result != null)
{
Attendance a = new Attendance();
a.Course_Id = courseId;
a.Student_Id = id;
a.Status = "P";
a.Date = DateTime.Today.Date;
a.Batch = batch;
a.Part = part;
db.Attendances.Add(a);
string message = "";
if (db.SaveChanges() != 0)
{
message = "Attendance Uploaded..!!\n";
}
else
{
message = "Attendance Not Uploaded ..!!\n";
}
this.Invoke((MethodInvoker)delegate()
{
progressBar1.Value = 100;
textBox2.AppendText(message);
});
}
else
{
this.BeginInvoke((MethodInvoker)delegate()
{
textBox2.AppendText("Student Does not belong to Specified Batch Or Part..\n");
});
}
}
}
else
{
this.Invoke((MethodInvoker)delegate()
{
textBox2.AppendText("No Match Found..!! \n");
});
}
this.Invoke((MethodInvoker)delegate()
{
textBox1.AppendText(text + "\n");
});
}
sp.Close();
// This exception will be handled by the Task
// and will not cause the program to crash
if (token.IsCancellationRequested)
{
//MessageBox.Show("Operation Comptele..!!");
}
}
You call MessageBox.Show("Operation Complete..!!"); in the progress of cancellation. This is highly not recommended, not talking about that you are calling an UI operation from other than the UI thread.
Comment the MessageBox.Show("Operation Complete..!!"); line out
* Edit *
Question author's comment on his original question, found the mistake, which line was removed from the post. Here are my conclusion:
Always try to isolate an issue, and reproduce in its purest form. During that process you will probably diagnose and find the issues itself :-).
So if the code with issue is long to post, it is definitely not the way just deleting lines then post it. The way is deleting lines and see if the issue exist, with other words: Isolating the issue in its purest reproducable form
please use async call on your task
private async void Start_Click(object sender, EventArgs e)
{
_task = Task.Factory.StartNew(() => EventLoop(_cts.Token), _cts.Token);
await task;
}
To stop The thread based on a flag Finesedequeue In the below thread method I did a loop in the stop button(its purpose is to stop this thread after it fills the queue named _textFromFilesQueue ) and the main problem in this is that my form is freezing
Here is my code:
The Threads Method:
public static bool Finisedqueue ;
public void Read(string inputDirectoryPath)
{
try
{
Thread.CurrentThread.IsBackground = true;
Finisedqueue = false;
Console.WriteLine("thread 1 started");
if (Form1.IsStarted)
{
Thread.Sleep(3000);
var dir = new DirectoryInfo(inputDirectoryPath);
FileInfo[] f = dir.GetFiles("*.txt");
if (f.Length > 0)
{
Console.WriteLine("{0} directory contains {1} Files",inputDirectoryPath,f.Length);
int counter = 0;
foreach (System.IO.FileInfo fi in f)
{
counter++;
var filePath = Path.Combine(fi.DirectoryName, fi.Name);
string textFromFile = File.ReadAllText(filePath);
_textFromFilesQueue.Enqueue(textFromFile);
Console.WriteLine("The text inside file number {0} is : {1}",counter,textFromFile);
fi.Delete();
Console.WriteLine("deleting file number {0} from input folder",counter);
}
Finisedqueue = true;
Console.WriteLine("finished Deleting files");
}
else
{
Console.WriteLine("{0} Has no files inside it ",inputDirectoryPath);
}
}
}
catch (Exception excep)
{
Console.WriteLine("An error occurred: '{0}'", excep);
throw(excep);
}
}
The Stop Button Click:
private void stop_Click(object sender, EventArgs e)
{
while (!Thread1.Finisedqueue)
{
Console.WriteLine("Queue from First Thread is still Enqueuing data");
}
Thread1Timer.Dispose();
button3.Enabled = true;
textBox3.Enabled = true;
}
> the main problem in this is that my form is freezing
IF any body can help me in this Thank you very much
When busy waiting it is a good idea to sleep, so that you give other threads a chance to execute.
while (!Thread1.Finisedqueue)
{
Thread.Sleep(100);
Console.WriteLine("Queue from First Thread is still Enqueuing data");
}
You can also check out the Task class
https://msdn.microsoft.com/en-us/library/system.threading.tasks.task(v=vs.110).aspx which is nice for asynchronous operations such as file IO and is awaitable using the built-in async/await functionality.
private Queue<string> q = new Queue<string>();
private Task readFilesTask;
public void ReadFiles(string inputDirectory)
{
var files = Directory.GetFiles(inputDirectory, "*.txt", SearchOption.TopDirectoryOnly);
foreach (var file in files)
{
var textFromFile = File.ReadAllText(file);
q.Enqueue(textFromFile);
File.Delete(file);
}
}
private void start_Click(object sender, EventArgs e)
{
readFilesTask = Task.Run(() => ReadFiles("//path/to/dir"));
}
private void stop_Click(object sender, EventArgs e)
{
readFilesTask.Wait();
//readFilesTask.Wait(1000); // if you want kill the task after waiting for 1000 milliseconds
//Enable buttons
}
it's my first question I'm asking here, so please be gentle with me ;)
So I've actually got two WinForms in my C# application I'm writing at the moment (I'm quite new to C#).
This window has a button, which saves photos from an usb device you selected before in a list box to another folder.
After clicking on this button my main thread is of course busy with copying, so I decided to create another WinForm which contains my ProgressBar.
Foreach completed copy, I want to increment my ProgressBar accordingly.
So I count the number of copies I have to do and give it the progressbar as maximum. But my problem at the moment is, that I really don't know how to increment the ProgressBar without getting a Thread Unsafe Exception.
Here's my ProgressWindow code:
public partial class ProgressWindow : Form
{
BackgroundWorker updateProgressBarThread = new BackgroundWorker();
private Boolean _isThreadRunning = false;
public Boolean IsThreadRunning
{
get { return _isThreadRunning; }
set { _isThreadRunning = value; }
}
private int _progressbarLength;
public int ProgressbarLength
{
get { return _progressbarLength; }
set { _progressbarLength = value; }
}
private int progress = 1;
public ProgressWindow()
{
Show();
InitializeComponent();
}
private void StartUpdateThread(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
// Reports progress to the ProgressChangedEvent function. (Thread Safe)
}
private void FinishProgressThread(object sender, RunWorkerCompletedEventArgs e)
{
if (!_isThreadRunning)
{
MessageBox.Show("Erfolgreich kopiert");
Close();
}
}
private void ProgressChangedEvent(object sender, ProgressChangedEventArgs e)
{
this.copyProgressbar.Value = e.ProgressPercentage;
this.progressStatus.Text = e.ProgressPercentage.ToString();
}
public void CallUpdateThread()
{
updateProgressBarThread.WorkerReportsProgress = true;
updateProgressBarThread.DoWork += new DoWorkEventHandler(StartUpdateThread);
updateProgressBarThread.ProgressChanged += new ProgressChangedEventHandler(ProgressChangedEvent);
updateProgressBarThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(FinishProgressThread);
updateProgressBarThread.RunWorkerAsync();
}
}
I want to increment my ProgressBar with 1 after each succesful copy.
How do I do this from my main thread?
This is the function which actually handles the copy process
private void SaveFile(System.IO.DirectoryInfo root)
{
try
{
IEnumerable<DirectoryInfo> directoriesNames = root.EnumerateDirectories();
// New instance of thread ProgressWindow.
ProgressWindow progress = new ProgressWindow();
progress.CallUpdateThread();
foreach (DirectoryInfo element in directoriesNames)
{
// Query all subdirectories and count everything with the in the configuration made settings.
if (!element.Attributes.ToString().Contains("System"))
{
// Now we insert the configuration we applied.
String fileExtension = null;
if (Properties.Settings.Default._configPhoto)
{
fileExtension = "*.jpg";
}
if (Properties.Settings.Default._configWordDocument)
{
fileExtension = "*.odt";
}
FileInfo[] jpgList = element.GetFiles(fileExtension, SearchOption.AllDirectories);
// set the size of the progress bar
progress.ProgressbarLength = jpgList.Count();
// Now we go through all our results and save them to our backup folder.
foreach (FileInfo tmp in jpgList)
{
string sourceFilePath = tmp.FullName;
string destFilePath = PATHTOBACKUP + "\\" + tmp.Name;
progress.IsThreadRunning = true;
try
{
System.IO.File.Copy(sourceFilePath, destFilePath, true);
}
catch (IOException ioe)
{
MessageBox.Show(ioe.Message);
}
}
}
}
// progress.IsThreadRunning = false;
}
catch (UnauthorizedAccessException e)
{
MessageBox.Show(e.Message);
}
}
It's pretty obvious that I have to do this after this function
System.IO.File.Copy(sourceFilePath, destFilePath, true);
But how do I report this to my ProgressWindow?
I really hope I explained it well enough, not sure if I'm missing something important.
Thanks in advance guys
Here is a compact example of the key components:
Clicking button starts new thread worker
Progress is done by file lengths, not by number of files
BeginInvoke used to update the progress bar (avoid cross Thread exception)
ProgressBar pb = new ProgressBar() { Minimum = 0, Maximum = 100 };
Button btn = new Button();
btn.Click += delegate {
Thread t = new Thread(() => {
DirectoryInfo dir = new DirectoryInfo("C:\\temp\\");
var files = dir.GetFiles("*.txt");
long totalLength = files.Sum(f => f.Length);
long length = 0;
foreach (var f in files) {
length += f.Length;
int percent = (int) Math.Round(100.0 * length / totalLength);
pb.BeginInvoke((Action) delegate {
pb.Value = percent;
});
File.Copy(f.FullName, "...");
}
});
t.IsBackground = true;
t.Start();
};
I think the title speaks for itself. Simply I have some threads that run with a random order instead of the order I planned.
This is a sample code:
event strHandler strChanged;
delegate void strHandler(string str);
public Form1()
{
InitializeComponent();
strChanged += new strHandler(updatestr);
}
public void updatestr(string str)
{
Thread th = new Thread(new ParameterizedThreadStart(updatethr));
th.IsBackground = true;
th.Start(str);
}
object obj = new object();
private void updatethr(object str)
{
lock (obj)
{
SystemUtilities.SetControlPropertyThreadSafe(textBox1, "Text", (string)str);
Thread.Sleep(1000);
}
}
private void button1_Click(object sender, EventArgs e)
{
this.Text = write();
}
private string write()
{
string res = "";
strChanged(res);
for (int i = 0; i <= 5; i++)
{
res += i.ToString();
strChanged(res);
}
return res;
}
Note: SystemUtilities.SetControlPropertyThreadSafe(textBox1, "Text", (string)str) is a function (used to avoid cross thread exception) that set textBox1.Text to str.
When you press button1 this.Text will be set instantly to the result of write() function ("012345").
The string returned is res that is build inside write() starting from an empty string and, iteratively, appending numbers from 0 to 5.
When the string is created and for each number added to res, the event strChanged is raised calling updatestr method.
Every time that updatestr is called a thread is created and it starts calling updatethr.
Here textBox1.Text is set to str (that should be progressively "", "0" , "01", "012", "0123", "01234", "012345") and wait a second before exiting the method.
Using lock statement the threads created in updatestr should wait the end of the previous threads before modifying textBox1.Text.
Running this code I obtain sequences of values for textBox1.Text that don't match the expected sequence as if the threads don't start in order with their creation in updatestr.
Why does this happen? How can I fix that? Thanks in advance!
EDIT: If want to try this code you can replace SystemUtilities.SetControlPropertyThreadSafe(textBox1, "Text", (string)str) with System.Windows.Forms.MessageBox.Show(str)
I believe you are looking for a different threading strategy. It appears you need a single thread, to maintain order, that's different from the Form's main thread to finish an operation. By using a BlockingCollection, you can sequentially have a different thread operate on the string.
I would rewrite the code this way:
event strHandler strChanged;
delegate void strHandler(string str);
public Form1()
{
InitializeComponent();
Thread th = new Thread(new ThreadStart(updatethr));
th.IsBackground = true;
th.Start();
strChanged += new strHandler(updatestr);
}
BlockingCollection<string> bc = new BlockingCollection<string>();
public void updatestr(string str)
{
bc.Add(str);
}
private void updatethr()
{
while(true)
{
string str = bc.Take();
SystemUtilities.SetControlPropertyThreadSafe(textBox1, "Text", (string)str);
// Not sure why you need this here, other than simulating a long operation.
// Thread.Sleep(1000);
}
}
private void button1_Click(object sender, EventArgs e)
{
this.Text = write();
}
private string write()
{
string res = "";
strChanged(res);
for (int i = 0; i <= 5; i++)
{
res += i.ToString();
strChanged(res);
}
return res;
}