I am developing an application which will actively read from a file and analyze and display information in that file to the UI in real time.
Everything I have read tells me I should use some kind of threading to do this. I have explored background workers and also tried creating a separate thread at run time and using that thread to update all of the UI elements.
The problem comes in when I can't (or shouldn't) be making cross thread calls, as the original UI elements were created on a different thread.
Is there a way to create these UI elements on the thread that will be updating them? What is the best way to do this?
edit: There was a reply to this post (which is now gone) explaining how I should do this. After updating my code with the method described
This is the updated code I have used. Everything is fine until I add in the file system watcher. As soon as I add that I get the same error about not making cross thread calls.
Session is a class I created that parses through the log file
private Session s1 = new Session("");
private FileSystemWatcher fsw;
private OpenFileDialog ofd1 = new OpenFileDialog();
private BackgroundWorker bgw;
private bool logActive = false;
public frmMain()
{
InitializeComponent();
bgw = new BackgroundWorker();
bgw.WorkerReportsProgress = true;
bgw.ProgressChanged += HandleProgressChanged;
bgw.DoWork += HandleDoWork;
fsw = new FileSystemWatcher(#"H:\Logs", "*.txt");
fsw.SynchronizingObject = this;
fsw.IncludeSubdirectories = false;
fsw.EnableRaisingEvents = true;
fsw.NotifyFilter = NotifyFilters.Size;
fsw.Changed += new FileSystemEventHandler(fsw_OnChanged);
}
private void frmMain_Load(object sender, EventArgs e)
{
ofd1.Filter = "log files (*.txt)|*.txt|All files (*.*)|*.*";
ofd1.FilterIndex = 2;
ofd1.RestoreDirectory = true;
}
private void fsw_OnChanged(object source, System.IO.FileSystemEventArgs e)
{
bgw.RunWorkerAsync();
}
// this runs on the UI thread
// here's where you update the UI based on the information from the event args
private void HandleProgressChanged(object sender, ProgressChangedEventArgs e)
{
for (int i = s1.previousLineNumber; i < s1.GetMessageCount(); i++)
{
ListViewItem lvi = new ListViewItem((s1.GetMessage(i).date).ToString());
lvi.SubItems.Add(s1.GetMessage(i).type.ToString());
lvi.SubItems.Add(s1.GetMessage(i).data);
listView1.Items.Add(lvi);
}
}
// this runs on a background thread; you cannot modify UI controls here
private void HandleDoWork(object sender, DoWorkEventArgs e)
{
s1.ParseLiveFile();
bgw.ReportProgress(100);
}
In order to update the UI you should use Invoke or BeginInvoke.
void LengthyProcessInThread()
{
...
foreach(var item in file)
{
Invoke(delegate() {
.. Update UI here.
});
}
}
Invoke is a method on a control, Eg. the form containing the UI.
I wish you good luck with your quest.
Related
Scenario: I have a DataGrid in my application in which onclikcing each row, the values get populated in the texboxes below. I'll update the values and when clicking save the process is taking more time to complete.
So i have written a backgroundworker to make the process run asynchronously. When each row is clikced an instance of a backgroundworker is created and the process is accomplished. During that update the user will select the second row of the grid and update that values. So this will create another instance and the process will run in background.
Now when both the update process is running if the user selects the first row of the grid there should be a message showing "The process is still running".
//Code:
' OnClick of the event
var bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += delegate {
SaveDetails();
};
bw.RunWorkerCompleted += delegate {
MessageBox.Show("Completed");
};
bw.RunWorkerAsync();
'Save method
public void SaveDetails()
{
for (int i = 0; i < 10;i++ )
{
System.Threading.Thread.Sleep(5000);
MessageBox.Show("Hi");
}
}
How can i access the previously created backgroundworker instance and check the status of the process and display the messgage?
Note: There may be many process running simultaneously, so i should be able to access any process.
Is this possible?
You can store any BackgrounWorker you create in a List to refer to them at any moment, but alternatively you can create a tasks Queue and store there any pending process, so you have only one BackgroundWorker at any time.
Storing the BackgroundWorkers in a List
Create a List(Of BackgroundWorker) which can be accesed anywhere you need in your code, for example. Every time you create a new BackgroundWorker, add it to the List:
mylist.Add(bw)
You a lot of options to access the correct backgroundworker later on. The easiest one is to create your own class which will have an identificator (the row of the DataGrid, for example) and the backgroundworker. This way, your list will be of this class instead of BackgroundWorkers:
myClass.BackgroundWorkerProperty = bw
myClass.id = myId
myList.Add(myClass)
Using a Queue to run the tasks
Create a Queue with a type which has the information to run the task. For example, the row of the DataGrid, if that's enought, that will be type Integer (the index), then add it everytime the backgroundworker is running:
myQueue.Add(myRow)
Everytime the backgroundworker finish, check the Queue and run the next task stored.
You can use the Tag property of the DataGridViewRow:
var bw = new BackgroundWorker();
row.Tag = bw;
So you can access it.
If you are using .NET 4.5 i would suggest to use Async/Await rather than Background thread to solve your problem, because its programming style is easy and efficient as given below:
public partial class Form1 : Form
{
List<Task<bool>> taskList = new List<Task<bool>>();
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
Task<bool> task = Task.Run(() => SaveDetails());
MessageBox.Show(task.Id + " started.");
taskList.Add(task);
var isSuccess = await task;
}
public bool SaveDetails()
{
for (int i = 0; i < 10; i++)
{
System.Threading.Thread.Sleep(5000);
//MessageBox.Show("Finishing.");
}
return true;
}
private void button2_Click(object sender, EventArgs e)
{
foreach (var task in taskList)
{
if (task.IsCompleted == true)
MessageBox.Show(task.Id + " Completed.");
}
}
}
i have created a second button click event to access and display all currently completed tasks.
Also, please make a note that you can use task.Id as the row identifier of your grid row.
Hope that helps.
well, that's rather easy. put the BackgroundWorker as a member in your form and then you can access it from everywhere:
BackgroundWorker bg;
public Form1()
{
bg = new BackgroundWorker();
}
private void button1_Click(object sender, EventArgs e)
{
if(bg.IsBusy)
{
// show the message
}
else
{
bw.WorkerReportsProgress = true;
bw.DoWork += delegate {
SaveDetails();
};
bw.RunWorkerCompleted += delegate {
MessageBox.Show("Completed");
};
bw.RunWorkerAsync();
}
}
I have a code that fetches tweets from a specific Twitter account using Tweetsharp library, creates instance of a custom UserControl and post tweet text to that UserControl then add it to a StackPanel.
However, I have to get a lot of tweets and it seems that the application would freeze while adding user controls to the StackPanel. I tried using BackgroundWorker, but I wasn't lucky until now.
My code :
private readonly BackgroundWorker worker = new BackgroundWorker();
// This ( UserControl ) is used in the MainWindow.xaml
private void UserControl_Loaded_1(object sender, RoutedEventArgs e)
{
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
int usrID;
var service = new TwitterService(ConsumerKey, ConsumerSecret);
service.AuthenticateWith(AccessToken, AccessTokenSecret);
ListTweetsOnUserTimelineOptions options = new ListTweetsOnUserTimelineOptions();
options.UserId = usrID;
options.IncludeRts = true;
options.Count = 10;
twitterStatuses = service.ListTweetsOnUserTimeline(options);
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
try
{
foreach (var item in twitterStatuses)
{
TweetViewer tweetViewer = new TweetViewer(); // A UserControl within another UserControl
tweetViewer.Tweet = item.Text;
tweetViewer.Username = "#stackoverflow";
tweetViewer.RealName = "Stack Overflow"
tweetViewer.Avatar = ImageSourcer(item.Author.ProfileImageUrl);
stackPanel.Children.Add(tweetViewer);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
What I want to do now is to solve the problem of not being able to perform the code contained in worker_RunWorkerCompleted within a BackgroundWorker but every time I try to perform it using a BackgroundWorker it fails & gives me errors like :
The calling thread must be STA, because many UI components require this.
I tried also using a STA System.Threading.Thread instead of the BackgroundWorker but without luck!
What am I missing ? I'm really new to WPF and I may be ignoring something important.
You get this exception because your background worker uses a new thread, and this thread is different than the main UI thread.
To simplify the error message says that you cannot change your UI element from another thread, they are independant.
This answer will solve your problem.
I also found this answer from #Marc Gravell
///...blah blah updating files
string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate {
someLabel.Text = newText; // runs on UI thread
});
///...blah blah more updating files
I have a problem in my WPF app. I have a custom CircularProgressBar. When I retrieve data from database it takes a few seconds.
I would like to show the CircularProgressBar in my app while the data is retrieved.
This code runs the CircularProgressBar :
CircularProgressBar cb = new CircularProgressBar();
stk.Children.Add(cb);
ThreadStart thStart = delegate()
{
ThreadStart inv = delegate()
{
stk.Children.Remove(cb);
};
this.Dispatcher.Invoke(inv, null);
};
Thread myThread = new Thread(thStart);
myThread.Start();
in my custom class (Printer).
And where I call this window:
Printer p = new Printer();
p.Show();
//Code For retrieve Data from DataBase
p.close();
So this happens : CircularProgressBar shows for a few seconds and it not running. Where is my bug?
You can simply use background worker:
private BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
//load data from database
System.Threading.Thread.Sleep(1);
worker.ReportProgress(progressbar_value);
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Progress.value= progressbar_value;
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//progress completed
}
This is not how you do stuff in wpf -
use a model to populate the data from the db and than bind CircularProgressBar visibility to
the state you're in (hide it when you completed the task).
all this boilerplate code should be in xaml.
If I were you, I would simplify life by using databinding with dependencyproperties.
What are the steps to follow.
1) Create a dependency property called IsBusyProperty of type bool in your custom progressbar.
2) Register a delegate to its value change event (this is done when you create the dependency property).
3) You can now bind this IsBusyProperty to a status in your code that says hey I am busy.
4) When the value is set to true you get your progressbar to start its magic.
5) When it is set to false you stop the magic.
It is far simpler to create a control with a storyboard that rotates, so long as your ui is not locked it will rotate then simply kill it afterward.
Try this
Im having some problems in my code:
private void start_watcher()
{
fswFiler = new FileSystemWatcher(Control.filer.get_path(),"*.*");
//fswStorage = new FileSystemWatcher(Control.storage.get_path());
fswFiler.Changed += new FileSystemEventHandler(updatePend);
fswFiler.Deleted += new FileSystemEventHandler(updatePend);
fswFiler.Created += new FileSystemEventHandler(updatePend);
fswFiler.Renamed += new RenamedEventHandler(updatePend);
fswFiler.EnableRaisingEvents = true;
}
private void updatePend(object sender, FileSystemEventArgs e)
{
this.viewPend.Nodes.Clear();
Control.filer.refresh_files_list();
this.viewPend.Nodes.Add(Control.filer.get_files_node());
}
throws me out of the program.
any idea why is that happening ?
The FileSystemWatcher notifications occur in another thread than the UI uses. You must Invoke
See: how to update a windows form GUI from another class?
Or even better: How to update the GUI from another thread in C#?
I'm using a BackgroundWorker to periodically check a hardware switch. Due to it is connected via a slow RS485 network, I have to delay the next status update.
On switch Status change I want to update an OK/nOK Picture Box. This is realized as a green OK pictureBox over a nOK pictureBox. No real work is done here.
For expandability I decided to use the Backgroundworker. Finally I want to have a hidden worker, which
provides globally the Status of three switches and
updates on StatusChange the PictureBoxes.
Problem description
Once the BackgroundWorker is started, it works as expected. However the GUI freezes.
What did I try?
The MSDN BackgroundWorker Class Note 1
says, that GUI should be updated via ProgressChanged. I tried to raise this Event by Worker_Switch.ReportProgress(fakeProgress++) and failed. The PictureBox wasn't updated anymore.
Snippet from designer
this.Worker_Switch = new System.ComponentModel.BackgroundWorker();
//
// Worker_Switch
//
this.Worker_Switch.WorkerSupportsCancellation = true;
this.Worker_Switch.DoWork += new System.ComponentModel.DoWorkEventHandler(this.Worker_Switch_DoWork);
Snippet from Main Form
delegate void SetEventCallback(object sender, DoWorkEventArgs e); // Threadsafe calls for DoWork
private void btnBackgroundworker_Click(object sender, EventArgs e)
{
if (!Worker_Switch.IsBusy)
{
Worker_Switch.RunWorkerAsync();
}
}
private void Worker_Switch_DoWork(object sender, DoWorkEventArgs e)
{
// Worker Thread has no permission to change PictureBox "pictureBoxSwitchrightOK"
// Therefore this method calls itsself in the MainThread, if necessary.
while (!Worker_Switch.CancellationPending)
{
if (this.pictureBoxSwitchrightOK.InvokeRequired) // Worker Thread
{
System.Threading.Thread.Sleep(400);
SetEventCallback myCall = new SetEventCallback(Worker_Switch_DoWork);
this.Invoke(myCall, new object[] { sender, e });
}
else // Main Thread
{
// Turns OK Picture Box invisible, if nOk State (Switch pushed)
pictureBoxSwitchrightOK.Visible = SwitchOK("right"); // true: OK (green)
this.Refresh();
}
}
private bool SwitchOK(string rightOrLeft) // select one of the switches
{ (...)} // gets hardware switch status
Edit: Special Thanks to laszlokiss88 (3 possibilities) and JMK (for simplicity with System.Windows.Forms Timer from toolbox)
This alternative from Toolbox also worked:
this.timer_Switch.Enabled = true;
this.timer_Switch.Interval = 400;
this.timer_Switch.Tick += new System.EventHandler(this.timer_Switch_Tick);
private void timer_Switch_Tick(object sender, EventArgs e)
{
motorSwitchControl.Init(); // globally available Switch status
SwitchRight = SwitchOK("right");
SwitchRightOK.Visible = SwitchRight;
SwitchLeft = SwitchOK("left"); // globally available Switch status
SwitchLeftOK.Visible = SwitchLeft;
SwitchAllOK = SwitchRight & SwitchLeft;
this.Refresh();
}
a) Is it correct, that the Sleep() actually happens in the Worker Thread?
- no Main Thread
b) What is going wrong, if I manipulate user interface objects in DoWork? (Contrary to MSDN Note)
- works in Main Thread?
c) What is the correct way to periodically update a PictureBox? DoWork, ProgressChanged, RunWorkerCompleted...?
- Three possibilities from laszlokiss88 answer.
You can update the UI from the DoWork event via the Dispatcher, or Control.Begininvoke(winforms), or you can do it via the ProgressChanged event of the BackgroundWorker:
public MainWindow()
{
InitializeComponent();
var bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerAsync();
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// You are in the main thread
// Update the UI here
string data = (string)e.UserState;
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
// You are in a worker thread
(sender as BackgroundWorker).ReportProgress(0, "right");
}
For a start you should almost never have a need to put an active background thead to sleep. I am also not sure why you are constructing/defining the delegate this way, try some thing like
public delegate void UpdatePictureBox();
myDelegate = new UpdatePictureBox(UpdatePictureboxMethod);
then you have a method UpdatePictureBoxMethod
private void UpdatePictureBoxMethod()
{
this.pictureBox1.Image = Properties.Resources.SomeImage;
}
or something simalar, where you pass in the image to update to.
Alternatively you could use the (bgWorker as BackgroundWorker).ReportProgress(progress, object); method. So from the background thread you call
(bgWorker as BackgroundWorker).ReportProgress(progressBarValue, infoBall);
where here class IfoBall will hold all your important information
class InfoBall
{
public int nProgressBar { get; set; }
public int nMaxProgressBar { get; set; }
public Image image { get; set; }
}
then you can pass this object back to the UI thread and do your updates
void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// On UI thread.
InfoBall someBall = (InfoBall)e.UserState;
this.pictureBox1.Image = someBall.image;
// etc...
}
I hope this helps.