Update UI from another thread work only one time - c#

I know there's a lot of information of this on stackoverflow, but don't find anything that resolved my problem.
I made a program to use ffmpeg with some video files. This process can take several minutes, so, I'm trying to make a progress bar on another form.
Basically, when I click on a button on my main form (FormSync), a new form is showed. This form have only a progressbar and a cancel button (lets call FormProgress).
To execute the ffmpeg, I use another class ( VideoProcessing) to create a new process, execute ffmpeg, and monitor the stderror (ffmpeg show progress on stderror). Every time ffmpeg show a progress, this class parse the output, calculate the progress, and raise a event (OnMergeProgress).
Basically, this is the code:
FormSync:
public partial class FormSync : Form
{
// this form show the progress of job
private FormProgress _formProgress;
// start the ffmpeg when click on button
private void mergeButton_click(object sender, EventArgs e)
{
var files = new List<string>() {"file1.mp4", "file2.mp4"};
MergeFiles(files);
}
// join all video files on a single file (using ffmpeg)
private void MergeFiles(IEnumerable<string> videoFiles)
{
// instantiate the class that execute ffmpeg
VideoProcessing videoProcessing = new VideoProcessing();
// this class has a a event to show current progress
// seconds = total seconds of video (sum length of all video files)
// currentSeconds = current progress
videoProcessing.OnMergeProgress += (seconds, currentSeconds) =>
{
Invoke((MethodInvoker) delegate()
{
// Instantiate the form of progress if not visible
if (_formProgress = null)
{
// define the minimum and maximum value of progressbar on constructor
_formProgress = new FormProgress(0, seconds);
_formProgress.ShowDialog(this);
}
// update the progress bar value
_formProgress.SetProgress(currentSeconds);
}
}
}
}
FormProgress:
public partial class FormProgress : Form
{
public FormProgress(int min, int max)
{
InitializeComponent();
progressBar.Minimum = min;
progressBar.Maximum = max;
}
public void SetProgress(int value)
{
value = (value <= progressBar.Minimum)
? progressBar.Minimum
: (value >= progressBar.Maximum) ? progressBar.Maximum : value;
progressBar.Value = value;
Refresh();
}
}
VideoProcessing:
internal class VideoProcessing
{
// Events
public delegate void MergeProgressHandler(int totalSeconds, int currentSeconds);
public event MergeProgressHandler OnMergeProgress;
private int _totalTimeVideos;
public void MergeFiles(string[] videoFiles)
{
// calculate total time of all videos
_totalTimeVideos = SomeFunctionToCalculateTotalTime();
// create the Process object to execute FFMPEG (with stdout and stderr redirection)
_process = CreateFFMPEGProcess(videoFiles);
}
// capture the stdout and stderr of ffmpeg
private void MergeOutputHandler(object sendingProcess, DataReceivedEventArgs outline)
{
// capture the current progress
// here will go a regex, and some other code to parse the info from ffmpeg
// Raise the event
OnMergeProgress?.Invoke(_totalTimeVideos, progressSeconds);
}
}
Basically, the FFMPEG execution and capture process use the following code:
C# execute external program and capture (stream) the output
The problem occur when I try to execute the code.
When I click que button, the FormProgress is showed, but after this, the progress bar "freeze". The program works good, no hangs here, but no update on progress bar.
If, in FormSync, on InvokeMethod, I replace the original code with the following content, I can see that ffmpeg is working, and my events are working too:
videoProcessing.OnMergeProgress += (seconds, currentSeconds) =>
{
Debug.WriteLine($"{currentSeconds}/{seconds}");
}
So, the problem was not ffmpeg, or my video class, but something that update the UI.
If I change the Invoke again, but this time with the Debug, like code below, the Debug print only the first update, and nothing more:
videoProcessing.OnMergeProgress += (seconds, currentSeconds) =>
{
Invoke((MethodInvoker) delegate() {
if (_formProgress == null) {
_formProgress = new FormProgress(Resources.merging_video_files, 0, seconds);
_formProgress.ShowDialog(this);
}
_formProgress.SetProgress(currentSeconds);
});
Debug.WriteLine($"{currentSeconds}/{seconds}");
}

_formProgress.ShowDialog(this);
The bug is located here. ShowDialog() does not return until the window is closed. Not clear when that happens, but not relevant to the bug. Since it does not return, the Invoke() call deadlocks and cannot complete. Which in turn causes the worker thread to hang.
Part of the problem is that the code uses Invoke() instead of Begininvoke(), you would not have noticed the same bug had you used the latter method. Not that this is pretty, but it would have hid the problem. Note that you don't need Invoke(), you don't need the return value and BeginInvoke() works just fine.
Ultimate reason you got this bug is because you needed to initialize the ProgressBar.Maximum property. Just don't do this, 100 is a good maximum value. Just a wee bit of math required, progress is now (100 * currentSeconds) / seconds.
You still need to call ShowDialog(), that's is a bit awkward, you can use the Load event to call MergeFiles().

Related

C# Trouble Using Safe Thead or Background Worker

Fairly frustrating since this seems to be well documented and the fact that I accomplished this before, but can't duplicate the same success. Sorry, I'll try to relate it all clearly.
Visual Studio, C# Form, One Main Form has text fields, among other widgets.
At one point we have the concept that we are "running" and therefore gathering data.
For the moment, I started a one second timer so that I can update simulated data into some fields. Eventually that one second timer will take the more rapid data and update it only once per second to the screen, that's the request for the application right now we update at the rate we receive which is a little over 70 Hz, they don't want it that way. In addition some other statistics will be computed and those should be the field updates. Therefore being simple I'm trying to just generate random data and update those fields at the 1 Hz rate. And then expand from that point.
Definition and management of the timer: (this is all within the same class MainScreen)
System.Timers.Timer oneSecondTimer;
public UInt32 run_time = 0;
public int motion = 5;
private void InitializeTimers()
{
this.oneSecondTimer = new System.Timers.Timer(1000);
this.oneSecondTimer.Elapsed += new System.Timers.ElapsedEventHandler(oneSecondTimer_elapsed);
}
public void start_one_second_timer()
{
run_time = 0;
oneSecondTimer.Enabled = true;
}
public void stop_one_second_timer()
{
oneSecondTimer.Enabled = false;
run_time = 0;
}
Random mot = new Random();
private void oneSecondTimer_elapsed(object source, System.Timers.ElapsedEventArgs e)
{
run_time++;
motion = mot.Next(1, 10);
this.oneSecondThread = new Thread(new ThreadStart(this.UpdateTextFields));
this.oneSecondThread.Start();
}
private void UpdateTextFields()
{
this.motionDisplay.Text = this.motion.ToString();
}
motionDisplay is just a textbox in my main form. I get the Invalid Operation Exception pointing me towards the help on how to make Thread-Safe calls. I also tried backgroundworker and end up with the same result. The details are that motionDisplay is accessed from a thread other than the thread it was created on.
So looking for some suggestions as to where my mistakes are.
Best Regards. I continue to iterate on this and will update if I find a solution.
Use a System.Forms.Timer rather than a System.Timers.Timer. It will fire it's elapsed event in the UI thread.
Don't create a new thread to update the UI; just do the update in the elapsed event handler.
Try this
private void UpdateTextFields()
{
this.BeginInvoke(new EventHandler((s,e)=>{
this.motionDisplay.Text = this.motion.ToString();
}));
}
This will properly marshall a call back to the main thread.
The thing with WinForm development is that all the controls are not thread safe. Even getting a property such as .Text from another thread can cause these type of errors to happen. To make it even more frustrating is that sometimes it will work at runtime and you won't get an exception, other times you will.
This is how I do it:
private delegate void UpdateMotionDisplayCallback(string text);
private void UpdateMotionDisplay(string text) {
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.motionDisplay.InvokeRequired) {
UpdateMotionDisplayCallback d = new UpdateMotionDisplayCallback(UpdateMotionDisplay);
this.Invoke(d, new object[] { text });
} else {
this.motionDisplay.Text = text;
}
}
When you want to update the text in motionDisplay just call:
UpdateMotionDisplay(this.motion.ToString())

ManualResetEvent wait doesn't release after being set

I'm downloading two JSON files from the webs, after which I want to allow loading two pages, but not before. However, the ManualResetEvent that is required to be set in order to load the page never "fires". Even though I know that it gets set, WaitOne never returns.
Method that launches the downloads:
private void Application_Launching(object sender, LaunchingEventArgs e)
{
PhoneApplicationService.Current.State["doneList"] = new List<int>();
PhoneApplicationService.Current.State["manualResetEvent"] = new ManualResetEvent(false);
Helpers.DownloadAndStoreJsonObject<ArticleList>("http://arkad.tlth.se/api/get_posts/", "articleList");
Helpers.DownloadAndStoreJsonObject<CompanyList>("http://arkad.tlth.se/api/get_posts/?postType=webbkatalog", "catalog");
}
The downloading method, that sets the ManualResetEvent
public static void DownloadAndStoreJsonObject<T>(string url, string objName)
{
var webClient = new WebClient();
webClient.DownloadStringCompleted += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Result))
{
var obj = ProcessJson<T>(e.Result);
PhoneApplicationService.Current.State[objName] = obj;
var doneList = PhoneApplicationService.Current.State["doneList"] as List<int>;
doneList.Add(0);
if (doneList.Count == 2) // Two items loaded
{
(PhoneApplicationService.Current.State["manualResetEvent"] as ManualResetEvent).Set(); // Signal that it's done
}
}
};
webClient.DownloadStringAsync(new Uri(url));
}
The waiting method (constructor in this case)
public SenastePage()
{
InitializeComponent();
if ((PhoneApplicationService.Current.State["doneList"] as List<int>).Count < 2)
{
(PhoneApplicationService.Current.State["manualResetEvent"] as ManualResetEvent).WaitOne();
}
SenasteArticleList.ItemsSource = (PhoneApplicationService.Current.State["articleList"] as ArticleList).posts;
}
If I wait before trying to access that constructor, it easily passes the if-statement and doesn't get caught in the WaitOne, but if I call it immediately, I get stuck, and it never returns...
Any ideas?
Blocking the UI thread must be prevented at all costs. Especially when downloading data: don't forget that your application is executing on a phone, which has a very instable network. If the data takes two minutes to load, then the UI will be freezed for two minutes. It would be an awful user experience.
There's many ways to prevent that. For instance, you can keep the same logic but waiting in a background thread instead of the UI thread:
public SenastePage()
{
// Write the XAML of your page to display the loading animation per default
InitializeComponent();
Task.Factory.StartNew(LoadData);
}
private void LoadData()
{
((ManualResetEvent)PhoneApplicationService.Current.State["manualResetEvent"]).WaitOne();
Dispatcher.BeginInvoke(() =>
{
SenasteArticleList.ItemsSource = ((ArticleList)PhoneApplicationService.Current.State["articleList"]).posts;
// Hide the loading animation
}
}
That's just a quick and dirty way to reach the result you want. You could also rewrite your code using tasks, and using Task.WhenAll to trigger an action when they're all finished.
Perhaps there is a logic problem. In the SenastePage() constructor you are waiting for the set event only if the doneList count is less than two. However, you don't fire the set event until the doneList count is equal to two. You are listening for the set event before it can ever fire.

Multithreading to speed up load times

I made a program that loads a bunch of computer information. In the Form_Load event I have it initialize 3 (that number will grow) panels of information. One that has a bunch of unit information seems to make the program load rather slowly. I've tried to speed it up a bunch by switching from WMI to using Native calls, which helped a bunch. Soon though I'm going to have network information posted as well. I used to load that panel but i disabled it for a little bit till I work out the bugs in my other panels. So while learning how I can use a seperate thread to update my battery information I figured that I might be able to create seperate threads in my unit information panel so that it might could load faster. I dont know that any of my information would cause concurrent issues, but i can work on that.
I want to start small so what if i change this
private void Form1_Load(object sender, EventArgs e)
{
unitInformationPanel1.PopulateUnitInformation();
batteryInformationPanel1.InitializeBatteries();
magStripeReaderPanel1.SetupPointOfSale();
}
to this
private void Form1_Load(object sender, EventArgs e)
{
Thread infoThread = new Thread(new ThreadStart(unitInformationPanel1.PopulateUnitInformation));
infoThread.Start();
batteryInformationPanel1.InitializeBatteries();
magStripeReaderPanel1.SetupPointOfSale();
}
would the info thread be terminated when populate unit info is done? or would it be better to move that thread creation into PopulateUnitInformation? here is what it looks like.
public void PopulateUnitInformation()
{
unitModelLabel.Text = Properties.Settings.Default.UnitModelString;
serialNumberLabel.Text = Properties.Settings.Default.UnitSerialString;
biosVersionLabel.Text = UnitBios.GetBiosNumber();
osLabel.Text = OS.getOSString();
cpuLabel.Text = UnitCpu.GetCpuInfo();
var hdd = HddInfo.GetHddInfo();
diskNameLabel.Text = hdd.Name;
diskCapacityLabel.Text = hdd.Capacity;
diskFirmwareLabel.Text = hdd.Firmware;
memoryLabel.Text = MemoryInformation.GetTotalMemory();
NetworkPresenceInformation.GetAdapatersPresent();
biometricLabel.Text = BiometricInformation.IsPresent ? "Present" : "Not Present";
var networkAdaptersPresense = NetworkPresenceInformation.GetAdapatersPresent();
bluetoothLabel.Text = networkAdaptersPresense[0] ? "Present" : "Not Present";
wifiLabel.Text = networkAdaptersPresense[1] ? "Present" : "Not Present";
cellularLabel.Text = networkAdaptersPresense[2] ? "Present" : "Not Present";
}
--
wow i just ran it with the infothread and it still took some time to load (might be the 12 panels i created in the main thread. but it loaded the 12 frames and the unit information panel populated its information after everything loaded. That was cool, but is it safe? is it somewhat easy to make 12 threads for my panels? or is that dumb?
EDIT
this is what i did for stopwatch.
Stopwatch programTimer;
public Form1()
{
programTimer = Stopwatch.StartNew();
InitializeComponent();
SetupDebugWindow();
TerminateKeymon();
UnitModel.SetModel();
UnitSerialNumber.SetSerialNumber();
}
private void Form1_Shown(object sender, EventArgs e)
{
audioBrightnessPanel1.UpdateBrightnessTrackbar();
applicationLauncherPanel1.LoadApplications();
programTimer.Stop();
Console.WriteLine("Load Time: {0}",programTimer.ElapsedMilliseconds);
timer1.Start();
}
Will this be accurate?
EDIT 2 6/18/2012
Well I took the advice of using backgroundworker. Please let me know if i did this right.
private void Form1_Load(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
void BackgroundWorker1DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
unitInformationPanel1.PopulateUnitInformation();
batteryInformationPanel1.InitializeBatteries();
magStripeReaderPanel1.SetupPointOfSale();
}
You've asked a very broad question, but I'm going to give some general advice. If you want more specific information, you should consider deleting this question and posting more specific individual questions.
First and foremost, you should very strongly consider using something like the System.Threading.Task class for your multithreaded operations. There is a ton of information online about how to get started with it and how you can use Tasks to manage asynchronous operations. The short story is that if you're spinning up your own thread (as you're doing above), you almost certainly should be using something else to do that for you.
Adding multithreading to your code will not, in the strictest sense of the word, make it any "faster"; they will always take the same amount of total processor time. What it can and will do is two things: free up the UI thread to be responsive and allow you to split that "total processor time" across multiple cores or processors, should those be available to the system. So, if you have operation X that takes 10 seconds to complete, then just shifting operation X to another thread will not make it complete any faster than 10 seconds.
No, what you are doing above is not safe. I'm assuming that somewhere you've turned off checking for cross-thread communication errors in your app? Otherwise, that code should throw an exception, assuming this is a WinForms or WPF application. This is one reason to use Tasks, as you can easily separate the part of your process that actually takes a long time (or isn't UI related), then add a task continuation that uses the results and populates the UI elements within a properly synchronized context.
So my final approach this was as follows. I felt that my Main Form was doing more than it should. Sticking with the single responsibility principle I decided that MainForm should only be responsible for one thing, showing and displaying all 12 panels (now down to 11, i turned one into a menu item). So moved all the multithreading out of mainform and into program.cs. I found that this was even a little more difficult. What I did find though was a simple solution that allows me to not even worry about multithreading at all. It was the Idle event. Here is what i chose to do.
[STAThread]
static void Main()
{
DateTime current = DateTime.Now;
DateTime today = new DateTime(2012,7,19);
TimeSpan span = current.Subtract(today);
if (span.Days<0)
{
MessageBox.Show("Please adjust Time then restart Aspects","Adjust Time");
Process.Start("timedate.cpl").WaitForExit();
}
else
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Idle += new EventHandler(Application_Idle);
mainForm = new MainForm();
mainForm.Closing += new CancelEventHandler(mainForm_Closing);
#if !DEBUG
TerminateKeymon();
StartSerial();
SetupDefaultValues();
EmbeddedMessageBox(0);
#endif
Application.Run(mainForm);
}
}
static void Application_Idle(object sender, EventArgs e)
{
Application.Idle -= Application_Idle;
mainForm.toolStripProgressBar1.Increment(1);
UnitInformation.SetupUnitInformation();
mainForm.toolStripProgressBar1.Increment(1);
Aspects.Unit.HddInfo.GetHddInfo();
mainForm.toolStripProgressBar1.Increment(1);
for (int i = 0; i < mainForm.Controls.Count; i++)
{
if (mainForm.Controls[i] is AbstractSuperPanel)
{
try
{
var startMe = mainForm.Controls[i] as AbstractSuperPanel;
startMe.StartWorking();
mainForm.toolStripProgressBar1.Increment(1);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + mainForm.Controls[i].ToString());
}
}
}
mainForm.toolStripProgressBar1.Value = 0;
}
to sum up what that does is is I add a idle listener event. Once the thead goes idle (basically meaning that Mainform is finished drawing and making all 12 panels and is showing on my desktop) I then kill the idle event listener and tell all my panels and classes to start working one at a time, updating my progress bar as I go. It works great. The load time is still the same as it was before, but there is window visibile after only a few seconds. Maybe not the best use of resources, but i think the solution is simple and straight forward.
I had a question somewhat related to this for Mobile app development a few months back (see How to write a Trigger?), and Marc "the man" Gravell posted back with a simple class that I modified to return data to my main application whenever the thread was complete.
The actual class I put into use has loads of pointless data (for you), so I'm going to paste in a revised version of Mr. Gravell's code using techniques which I used to make them work:
First, I had to create my own EventArgs class:
public class SuperEventArgs : EventArgs {
private object data;
public SuperEventArgs(object data) : base() {
this.data = data;
}
public object Data { get { return data; } }
}
Using that, here is a class I created to pass my data back to the main thread:
public delegate event DataChangedHandler(object sender, SuperEventArgs e);
public class Simple1 {
private object parameter1, parameter2;
private Control parent;
#if PocketPC
public delegate void MethodInvoker(); // include this if it is not defined
#endif
public Simple1(Control frmControl, object param1, object param2) {
parent = frmControl;
parameter1 = param1;
parameter2 = param2;
}
public event DataChangedHandler DataChanged;
public void Start() {
object myData = new object(); // whatever this is. DataTable?
try {
// long routine code goes here
} finally {
if (DataChanged != null) {
SuperEventArgs e = new SuperEventArgs(myData);
MethodInvoker methInvoker = delegate {
DataChanged(this, e);
};
try {
parent.BeginInvoke(methInvoker);
} catch (Exception err) {
Log(err); // something you'd write
}
}
}
}
}
Back in the actual main thread of execution, you'd do something like this:
public partial class Form1 : Form {
private Simple1 simple;
public Form1() {
object query = new object(); // something you want to pass in
simple = new Simple1(this, query, DateTime.Now);
simple.DataChanged += new DataChangedHandler(simple1_DataChanged);
Thread thread = new Thread(simpleStart);
thread.Start();
}
private void simpleStart() {
if (simple != null) {
simple.Start();
}
}
private void simple1_DataChanged(object sender, SuperEventArgs e) {
MyFancyData fancy = e.Data as MyFancyData;
if (fancy != null) {
// populate your form with the data you received.
}
}
}
I know it looks long, but it works really well!
This is not anything I have actually tested, of course, because there isn't any data. If you get to working with it and you experience any issues, let me know and I'll happily help you work through them.
~JoeP

c# - Pass information to BackgroundWorker From UI during execution

I have a c# application that uses a background worker thread, and quite successfully updates the UI from the running thread. The application involves shortest path routing on a network, and I display the network and the shortest path, on the UI, as the background worker proceeds. I would like to allow the user to slow down the display through use of a slider, while the application is running.
I found this as a suggestion, but it is in vb.net, I am not clear on how to get it to work in c#.
How can the BackgroundWorker get values from the UI thread while it is running?
I can pass the value of the slider to the backgroundworker as follows:
// Start the asynchronous operation.
delay = this.trackBar1.Value;
backgroundWorker1.RunWorkerAsync(delay);
and use it within the backgroundworker thread, but it only uses the initially-sent value. I am not clear on how to pick up the value from inside the backgroundworker when I move the slider on the UI.
I have previously used multiple threads and delegates, but if it is possible to utilize the background worker, I would prefer it for its simplicity.
5/10/2012
Thanks to all for your responses. I am still having problems, most likely because of how I have structured things. The heavy duty calculations for network routing are done in the TransportationDelayModel class. BackgroundWorker_DoWork creates an instance of this class, and then kicks it off. The delay is handled in TransportationDelayModel.
The skeleton of code is as follows:
In UI:
private void runToolStripMenuItem1_Click(object sender, EventArgs e)
{
if (sqliteFileName.Equals("Not Set"))
{
MessageBox.Show("Database Name Not Set");
this.chooseDatabaseToolStripMenuItem_Click(sender, e);
}
if (backgroundWorker1.IsBusy != true)
{
// Start the asynchronous operation.
delay = this.trackBar1.Value;
// pass the initial value of delay
backgroundWorker1.RunWorkerAsync(delay);
// preclude multiple runs
runToolStripMenuItem1.Enabled = false;
toolStripButton2.Enabled = false;
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if (!backgroundWorkerLaunched)
{
// instantiate the object that does all the heavy work
TransportationDelayModel TDM = new TransportationDelayModel(worker, e);
// kick it off
TDM.Run(sqliteFileName, worker, e);
backgroundWorkerLaunched = true;
}
}
The TransportationDelayModel constructor is:
public TransportationDelayModel(BackgroundWorker worker, DoWorkEventArgs e)
{
listCentroids = new List<RoadNode>();
listCentroidIDs = new List<int>();
listNodes = new List<RoadNode>();
listNodeIDs = new List<int>();
listRoadLink = new List<RoadLink>();
roadGraph = new AdjacencyGraph<int, RoadLink>(true); // note parallel edges allowed
tdmWorker = worker;
tdmEvent = e;
networkForm = new NetworkForm();
}
so I have the tdmWorker, which allows me to pass information back to the UI.
In the internal calculations in TransportationDelayModel, I sleep for the delay period
if (delay2 > 0)
{
tdmWorker.ReportProgress(-12, zzz);
System.Threading.Thread.Sleep(delay2);
}
so the problem seems to be how to pass an updated slider value from the UI back to the object that is executing in the background worker. I have tried a number of combinations, sort of thrashing around, to no avail, either nothing happens or I get a message about not being allowed to access what is happening on the other thread. I realize that if I were doing all the work in the DoWork event handler, then I should be able to do things as you suggest, but there is too much complexity for that to happen.
Again, thank you for your suggestions and help.
6/2/2012
I have resolved this problem by two methods, but I have some questions. Per my comment to R. Harvey, I have built a simple application. It consists of a form with a run button, a slider, and a rich text box. The run button launches a background worker thread that instantiates an object of class "Model" that does all the work (a simplified surrogate for my TransportationModel). The Model class simply writes 100 lines to the text box, incrementing the number of dots in each line by 1, with a delay between each line based on the setting of the slider, and the slider value at the end of the line, something like this:
....................58
.....................58
......................58
.......................51
........................44
.........................44
The objective of this exercise is to be able to move the slider on the form while the "Model" is running, and get the delay to change (as in above).
My first solution involves the creation of a Globals class, to hold the value of the slider:
class Globals
{
public static int globalDelay;
}
then, in the form, I update this value whenever the trackbar is scrolled:
private void trackBar1_Scroll(object sender, EventArgs e)
{
Globals.globalDelay = this.trackBar1.Value;
}
and in the Model, I just pick up the value of the global:
public void Run(BackgroundWorker worker, DoWorkEventArgs e)
{
for (int i = 1; i < 100; i++)
{
delay = Globals.globalDelay; // revise delay based on static global set on UI
System.Threading.Thread.Sleep(delay);
worker.ReportProgress(i);
string reportString = ".";
for (int k = 0; k < i; k++)
{
reportString += ".";
}
reportString += delay.ToString();
worker.ReportProgress(-1, reportString);
}
}
}
This works just fine.
My question: are there any drawbacks to this approach, which seems very simple to implement and quite general.
The second approach, based on suggestions by R. Harvey, makes use of delegates and invoke.
I create a class for delegates:
public class MyDelegates
{
public delegate int DelegateCheckTrackBarValue(); // create the delegate here
}
in the form, I create:
public int CheckTrackBarValue()
{
return this.trackBar1.Value;
}
and the Model class now has a member m_CheckTrackBarValue
public class Model
{
#region Members
Form1 passedForm;
public static MyDelegates.DelegateCheckTrackBarValue m_CheckTrackBarValue=null;
#endregion Members
#region Constructor
public Model(BackgroundWorker worker, DoWorkEventArgs e, Form1 form)
{
passedForm = form;
}
When the background thread is launched by the run button, the calling form is passed
private void button1_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy != true)
{
backgroundWorker1.RunWorkerAsync();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if (!backgroundWorkerLaunched)
{
// instantiate the object that does all the heavy work
Model myModel= new Model(worker, e, this);
Model.m_CheckTrackBarValue = new MyDelegates.DelegateCheckTrackBarValue(this.CheckTrackBarValue);
// kick it off
myModel.Run(worker, e);
backgroundWorkerLaunched = true;
}
}
Finally, in the Model, the Invoke method is called on the passed form to get the value of the trackbar.
public void Run(BackgroundWorker worker, DoWorkEventArgs e)
{
for (int i = 1; i < 100; i++)
{
int delay = (int)passedForm.Invoke(m_CheckTrackBarValue,null); // invoke the method, note need the cast here
System.Threading.Thread.Sleep(delay);
worker.ReportProgress(i);
string reportString = ".";
for (int k = 0; k < i; k++)
{
reportString += ".";
}
reportString += delay.ToString();
worker.ReportProgress(-1, reportString);
}
}
This works as well. I kept getting an error until I made the member variable static, e.g.
public static MyDelegates.DelegateCheckTrackBarValue m_CheckTrackBarValue=null;
My questions on this solution: Are there advantages to this solution as regards to the previous version? Am I making things too complicated in the way I have implemented this? Why does m_CheckTrackBarValue need to be static.
I apologize for the length of this edit, but I thought that the problem and solutions might be of interest to others.
You have to pass the TrackBar object to the BackgroundWorker, not delay. delay doesn't change once you set it.
To simplify the needed Invoke(), you can use a helper method, such as this one:
Async.UI(delegate { textBox1.Text = "This is way easier!"; }, textBox1, true);
I will assume that you are already familiarized with cross-thread invocation to update the UI. So, the solution is very simple: in your worker thread, after each iteration, invoke the UI to get the slider thumb position.
To use a backgroundworker, you add a method to the DoWork property, like this:
this.backgroundWorker1.WorkerSupportsCancellation = true;
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
In the DoWork method, you need to check the variable where the updated delay is set.
This could be an integer field that is available on the containing Form or UI control, or it could be the TrackBar itself.

How to update UI from another thread running in another class

I am currently writing my first program on C# and I am extremely new to the language (used to only work with C so far). I have done a lot of research, but all answers were too general and I simply couldn't get it t work.
So here my (very common) problem:
I have a WPF application which takes inputs from a few textboxes filled by the user and then uses that to do a lot of calculations with them. They should take around 2-3 minutes, so I would like to update a progress bar and a textblock telling me what the current status is.
Also I need to store the UI inputs from the user and give them to the thread, so I have a third class, which I use to create an object and would like to pass this object to the background thread.
Obviously I would run the calculations in another thread, so the UI doesn't freeze, but I don't know how to update the UI, since all the calculation methods are part of another class.
After a lot of reasearch I think the best method to go with would be using dispatchers and TPL and not a backgroundworker, but honestly I am not sure how they work and after around 20 hours of trial and error with other answers, I decided to ask a question myself.
Here a very simple structure of my program:
public partial class MainWindow : Window
{
public MainWindow()
{
Initialize Component();
}
private void startCalc(object sender, RoutedEventArgs e)
{
inputValues input = new inputValues();
calcClass calculations = new calcClass();
try
{
input.pota = Convert.ToDouble(aVar.Text);
input.potb = Convert.ToDouble(bVar.Text);
input.potc = Convert.ToDouble(cVar.Text);
input.potd = Convert.ToDouble(dVar.Text);
input.potf = Convert.ToDouble(fVar.Text);
input.potA = Convert.ToDouble(AVar.Text);
input.potB = Convert.ToDouble(BVar.Text);
input.initStart = Convert.ToDouble(initStart.Text);
input.initEnd = Convert.ToDouble(initEnd.Text);
input.inita = Convert.ToDouble(inita.Text);
input.initb = Convert.ToDouble(initb.Text);
input.initc = Convert.ToDouble(initb.Text);
}
catch
{
MessageBox.Show("Some input values are not of the expected Type.", "Wrong Input", MessageBoxButton.OK, MessageBoxImage.Error);
}
Thread calcthread = new Thread(new ParameterizedThreadStart(calculations.testMethod);
calcthread.Start(input);
}
public class inputValues
{
public double pota, potb, potc, potd, potf, potA, potB;
public double initStart, initEnd, inita, initb, initc;
}
public class calcClass
{
public void testmethod(inputValues input)
{
Thread.CurrentThread.Priority = ThreadPriority.Lowest;
int i;
//the input object will be used somehow, but that doesn't matter for my problem
for (i = 0; i < 1000; i++)
{
Thread.Sleep(10);
}
}
}
I would be very grateful if someone had a simple explanation how to update the UI from inside the testmethod. Since I am new to C# and object oriented programming, too complicated answers I will very likely not understand, I'll do my best though.
Also if someone has a better idea in general (maybe using backgroundworker or anything else) I am open to see it.
First you need to use Dispatcher.Invoke to change the UI from another thread and to do that from another class, you can use events.
Then you can register to that event(s) in the main class and Dispatch the changes to the UI and in the calculation class you throw the event when you want to notify the UI:
class MainWindow : Window
{
private void startCalc()
{
//your code
CalcClass calc = new CalcClass();
calc.ProgressUpdate += (s, e) => {
Dispatcher.Invoke((Action)delegate() { /* update UI */ });
};
Thread calcthread = new Thread(new ParameterizedThreadStart(calc.testMethod));
calcthread.Start(input);
}
}
class CalcClass
{
public event EventHandler ProgressUpdate;
public void testMethod(object input)
{
//part 1
if(ProgressUpdate != null)
ProgressUpdate(this, new YourEventArgs(status));
//part 2
}
}
UPDATE:
As it seems this is still an often visited question and answer I want to update this answer with how I would do it now (with .NET 4.5) - this is a little longer as I will show some different possibilities:
class MainWindow : Window
{
Task calcTask = null;
void buttonStartCalc_Clicked(object sender, EventArgs e) { StartCalc(); } // #1
async void buttonDoCalc_Clicked(object sender, EventArgs e) // #2
{
await CalcAsync(); // #2
}
void StartCalc()
{
var calc = PrepareCalc();
calcTask = Task.Run(() => calc.TestMethod(input)); // #3
}
Task CalcAsync()
{
var calc = PrepareCalc();
return Task.Run(() => calc.TestMethod(input)); // #4
}
CalcClass PrepareCalc()
{
//your code
var calc = new CalcClass();
calc.ProgressUpdate += (s, e) => Dispatcher.Invoke((Action)delegate()
{
// update UI
});
return calc;
}
}
class CalcClass
{
public event EventHandler<EventArgs<YourStatus>> ProgressUpdate; // #5
public TestMethod(InputValues input)
{
//part 1
ProgressUpdate.Raise(this, status); // #6 - status is of type YourStatus
// alternative version to the extension for C# 6+:
ProgressUpdate?.Invoke(this, new EventArgs<YourStatus>(status));
//part 2
}
}
static class EventExtensions
{
public static void Raise<T>(this EventHandler<EventArgs<T>> theEvent,
object sender, T args)
{
if (theEvent != null)
theEvent(sender, new EventArgs<T>(args));
}
}
#1) How to start the "synchronous" calculations and run them in the background
#2) How to start it "asynchronous" and "await it": Here the calculation is executed and completed before the method returns, but because of the async/await the UI is not blocked (BTW: such event handlers are the only valid usages of async void as the event handler must return void - use async Task in all other cases)
#3) Instead of a new Thread we now use a Task. To later be able to check its (successfull) completion we save it in the global calcTask member. In the background this also starts a new thread and runs the action there, but it is much easier to handle and has some other benefits.
#4) Here we also start the action, but this time we return the task, so the "async event handler" can "await it". We could also create async Task CalcAsync() and then await Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false); (FYI: the ConfigureAwait(false) is to avoid deadlocks, you should read up on this if you use async/await as it would be to much to explain here) which would result in the same workflow, but as the Task.Run is the only "awaitable operation" and is the last one we can simply return the task and save one context switch, which saves some execution time.
#5) Here I now use a "strongly typed generic event" so we can pass and receive our "status object" easily
#6) Here I use the extension defined below, which (aside from ease of use) solve the possible race condition in the old example. There it could have happened that the event got null after the if-check, but before the call if the event handler was removed in another thread at just that moment. This can't happen here, as the extensions gets a "copy" of the event delegate and in the same situation the handler is still registered inside the Raise method.
I am going to throw you a curve ball here. If I have said it once I have said it a hundred times. Marshaling operations like Invoke or BeginInvoke are not always the best methods for updating the UI with worker thread progress.
In this case it usually works better to have the worker thread publish its progress information to a shared data structure that the UI thread then polls at regular intervals. This has several advantages.
It breaks the tight coupling between the UI and worker thread that Invoke imposes.
The UI thread gets to dictate when the UI controls get updated...the way it should be anyway when you really think about it.
There is no risk of overrunning the UI message queue as would be the case if BeginInvoke were used from the worker thread.
The worker thread does not have to wait for a response from the UI thread as would be the case with Invoke.
You get more throughput on both the UI and worker threads.
Invoke and BeginInvoke are expensive operations.
So in your calcClass create a data structure that will hold the progress information.
public class calcClass
{
private double percentComplete = 0;
public double PercentComplete
{
get
{
// Do a thread-safe read here.
return Interlocked.CompareExchange(ref percentComplete, 0, 0);
}
}
public testMethod(object input)
{
int count = 1000;
for (int i = 0; i < count; i++)
{
Thread.Sleep(10);
double newvalue = ((double)i + 1) / (double)count;
Interlocked.Exchange(ref percentComplete, newvalue);
}
}
}
Then in your MainWindow class use a DispatcherTimer to periodically poll the progress information. Configure the DispatcherTimer to raise the Tick event on whatever interval is most appropriate for your situation.
public partial class MainWindow : Window
{
public void YourDispatcherTimer_Tick(object sender, EventArgs args)
{
YourProgressBar.Value = calculation.PercentComplete;
}
}
You're right that you should use the Dispatcher to update controls on the UI thread, and also right that long-running processes should not run on the UI thread. Even if you run the long-running process asynchronously on the UI thread, it can still cause performance issues.
It should be noted that Dispatcher.CurrentDispatcher will return the dispatcher for the current thread, not necessarily the UI thread. I think you can use Application.Current.Dispatcher to get a reference to the UI thread's dispatcher if that's available to you, but if not you'll have to pass the UI dispatcher in to your background thread.
Typically I use the Task Parallel Library for threading operations instead of a BackgroundWorker. I just find it easier to use.
For example,
Task.Factory.StartNew(() =>
SomeObject.RunLongProcess(someDataObject));
where
void RunLongProcess(SomeViewModel someDataObject)
{
for (int i = 0; i <= 1000; i++)
{
Thread.Sleep(10);
// Update every 10 executions
if (i % 10 == 0)
{
// Send message to UI thread
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
(Action)(() => someDataObject.ProgressValue = (i / 1000)));
}
}
}
Everything that interacts with the UI must be called in the UI thread (unless it is a frozen object). To do that, you can use the dispatcher.
var disp = /* Get the UI dispatcher, each WPF object has a dispatcher which you can query*/
disp.BeginInvoke(DispatcherPriority.Normal,
(Action)(() => /*Do your UI Stuff here*/));
I use BeginInvoke here, usually a backgroundworker doesn't need to wait that the UI updates. If you want to wait, you can use Invoke. But you should be careful not to call BeginInvoke to fast to often, this can get really nasty.
By the way, The BackgroundWorker class helps with this kind of taks. It allows Reporting changes, like a percentage and dispatches this automatically from the Background thread into the ui thread. For the most thread <> update ui tasks the BackgroundWorker is a great tool.
If this is a long calculation then I would go background worker. It has progress support. It also has support for cancel.
http://msdn.microsoft.com/en-us/library/cc221403(v=VS.95).aspx
Here I have a TextBox bound to contents.
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Debug.Write("backgroundWorker_RunWorkerCompleted");
if (e.Cancelled)
{
contents = "Cancelled get contents.";
NotifyPropertyChanged("Contents");
}
else if (e.Error != null)
{
contents = "An Error Occured in get contents";
NotifyPropertyChanged("Contents");
}
else
{
contents = (string)e.Result;
if (contentTabSelectd) NotifyPropertyChanged("Contents");
}
}
You are going to have to come back to your main thread (also called UI thread) in order to update the UI.
Any other thread trying to update your UI will just cause exceptions to be thrown all over the place.
So because you are in WPF, you can use the Dispatcher and more specifically a beginInvoke on this dispatcher. This will allow you to execute what needs done (typically Update the UI) in the UI thread.
You migh also want to "register" the UI in your business, by maintaining a reference to a control/form, so you can use its dispatcher.
Thank God, Microsoft got that figured out in WPF :)
Every Control, like a progress bar, button, form, etc. has a Dispatcher on it. You can give the Dispatcher an Action that needs to be performed, and it will automatically call it on the correct thread (an Action is like a function delegate).
You can find an example here.
Of course, you'll have to have the control accessible from other classes, e.g. by making it public and handing a reference to the Window to your other class, or maybe by passing a reference only to the progress bar.
Felt the need to add this better answer, as nothing except BackgroundWorker seemed to help me, and the answer dealing with that thus far was woefully incomplete. This is how you would update a XAML page called MainWindow that has an Image tag like this:
<Image Name="imgNtwkInd" Source="Images/network_on.jpg" Width="50" />
with a BackgroundWorker process to show if you are connected to the network or not:
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
public partial class MainWindow : Window
{
private BackgroundWorker bw = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
// Set up background worker to allow progress reporting and cancellation
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
// This is your main work process that records progress
bw.DoWork += new DoWorkEventHandler(SomeClass.DoWork);
// This will update your page based on that progress
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
// This starts your background worker and "DoWork()"
bw.RunWorkerAsync();
// When this page closes, this will run and cancel your background worker
this.Closing += new CancelEventHandler(Page_Unload);
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
BitmapImage bImg = new BitmapImage();
bool connected = false;
string response = e.ProgressPercentage.ToString(); // will either be 1 or 0 for true/false -- this is the result recorded in DoWork()
if (response == "1")
connected = true;
// Do something with the result we got
if (!connected)
{
bImg.BeginInit();
bImg.UriSource = new Uri("Images/network_off.jpg", UriKind.Relative);
bImg.EndInit();
imgNtwkInd.Source = bImg;
}
else
{
bImg.BeginInit();
bImg.UriSource = new Uri("Images/network_on.jpg", UriKind.Relative);
bImg.EndInit();
imgNtwkInd.Source = bImg;
}
}
private void Page_Unload(object sender, CancelEventArgs e)
{
bw.CancelAsync(); // stops the background worker when unloading the page
}
}
public class SomeClass
{
public static bool connected = false;
public void DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
int i = 0;
do
{
connected = CheckConn(); // do some task and get the result
if (bw.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
Thread.Sleep(1000);
// Record your result here
if (connected)
bw.ReportProgress(1);
else
bw.ReportProgress(0);
}
}
while (i == 0);
}
private static bool CheckConn()
{
bool conn = false;
Ping png = new Ping();
string host = "SomeComputerNameHere";
try
{
PingReply pngReply = png.Send(host);
if (pngReply.Status == IPStatus.Success)
conn = true;
}
catch (PingException ex)
{
// write exception to log
}
return conn;
}
}
For more information: https://msdn.microsoft.com/en-us/library/cc221403(v=VS.95).aspx

Categories