GTK GUI freezes with async/await and Progress<T> - c#

I'm using GTK# to build a GUI. Some data is processed in the background and I'd like to see some info about the progress on the user interface. Here is some code demonstrating the way I am trying to do this:
using System;
using Gtk;
using System.Threading.Tasks;
using System.Threading;
public partial class MainWindow: Gtk.Window
{
//a button and a textfield
private VBox VB = new VBox();
private Button B = new Button("Push dis");
private Label L = new Label("0");
public MainWindow () : base (Gtk.WindowType.Toplevel)
{
B.Clicked += OnClickEvent;
////////////////////
VB.PackStart (B);
VB.PackStart (L);
Add (VB);
ShowAll ();
Build ();
}
protected void OnDeleteEvent (object sender, DeleteEventArgs a)
{
Application.Quit ();
a.RetVal = true;
}
//async method incrementing variable, simulating some work and sending its progress
protected async Task CounterGUIUpdateAsync(IProgress<string> progress)
{
await Task.Run (() => {
for (int i = 0; i <= 10000; i++) {
Thread.Sleep (100);
if(progress != null)
{
var stri = Convert.ToString(i);
progress.Report(stri);
}
}
});
}
//event handler for the button
protected async void OnClickEvent(object sender, EventArgs e)
{
var ProgressIndicator = new Progress<string> (ReportProgress);
await CounterGUIUpdateAsync (ProgressIndicator);
}
//action connected to the progress instance
protected void ReportProgress(string value)
{
L.Text = value;
}
}
Running the code will start off as expected, but at some point it is likely that the displayed counter gets stuck. The GUI won't update anymore, turning black if it has been minimized. It is still functional though.
Help is appreciated very much.

I think your problem is that you're not using the main thread (the gui thread) when using the Gtk+ API. You need to use Gtk.Application.Invoke() passing a delegate that manipulates the UI, so the action is performed in the correct thread.
You can read more about this here.

Related

How to load the progressbar at the same time my method is running

I have a long running method, so I created a progress bar to show what percentage my method was to completing, but I am having difficulty figuring out how to do this ,syncing my progress bar with my method => excelHelper.InsertNewRows();.
public void ButtonSubmit_Click(object sender, EventArgs e)
{
if (isProcessRunning)
{
MessageBox.Show("A Process is aleady running");
return;
}
Thread backgroundThread = new Thread(
new ThreadStart(() =>
{
for (int n = 0; n < 100; n++)
{
isProcessRunning = true;
Thread.Sleep(50);
progressBar1.Invoke(
new Action(() =>
{
progressBar1.Value = n;
label3.Text = ("Progress: " + n + "%");
}
));
}
MessageBox.Show("Thread completed!");
progressBar1.Invoke(
new Action(() =>
{
progressBar1.Value = 0;
}
));
isProcessRunning = false;
}
));
backgroundThread.Start();
excelHelper.InsertNewRows();
var folder = TextOutputFile.Text + #"\" +
DateTime.Now.ToString("yyyy_MM_dd_") + "SA_Analysis_Report.xlsx";
excelHelper.Save(folder);
MessageBox.Show("File has been added to file");
}
IMO your problem is, that you dont have any communication between the working thread excelHelper.InsertNewRows(); and your progressbar thread. Both threads are running without any information about the other thread and their progress.
You could rewrite the background thread, so its capable of taking the percentage as a parameter, which is shown in the moment you call the thread.
E.g. Pseudo Code:
public void progressbarThread(int percentage)
{
// Invoke the percentage to your progressbar, then terminate this thread
}
public void InsertNewRows()
{
// Do something...
// 10%
Thread backgroundThread = new Thread(new ThreadStart(() => progressBarThread(10)));
backgroundThread.Start();
// Do something...
// 50%
Thread backgroundThread = new Thread(new ThreadStart(() => progressBarThread(50)));
backgroundThread.Start();
// etc. etc.
}
Update:
I've found this on my own research how to build a smooth loadingbar with an extra form, it maybe useful: Form in an extra Thread
I use this form class: -
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Yournamespace
{
public partial class frmProgressBar : Form
{
public Action Worker { get; set; }
System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
public const long MINIMUM_DISPLAY_TIME = 300; // Minimum time to display the progress bar is 300 milliseconds
public frmProgressBar(Action worker)
{
InitializeComponent();
if(worker == null)
{
throw new ArgumentNullException();
}
Worker = worker;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Task.Factory.StartNew(Worker).ContinueWith(t => { this.Close(); }, TaskScheduler.FromCurrentSynchronizationContext());
}
protected override void OnClosed(EventArgs e)
{
// the code that you want to measure comes here
watch.Stop();
var elapsedMs = watch.ElapsedMilliseconds;
if (elapsedMs < MINIMUM_DISPLAY_TIME) //ensure progress bar is displayed for at least 100 milliseconds, otherwise it just looks like the parent main form glitches.
{
long lDelay = MINIMUM_DISPLAY_TIME - elapsedMs;
Thread.Sleep((int)lDelay);
}
}
}
}
The form design looks like this : -
[![enter image description here][1]][1]
[1]: https://i.stack.imgur.com/n4XqU.jpg
And I call it like this: -
using (Yournamespace.frmProgressBar frm = new Yournamespace.frmProgressBar(Yourprocess))
{
frm.ShowDialog(this);
}
Yourprocess is the code that you want to execute that is causing the delay.
The main problem with this implementation is Yourprocess cannot return a value or take parameters. I am sure the code can be changed to accomodate this but I did not have time so I use globals to pass in data and to see results(shame on me).
This is not my code, although I have modified it, came from a you tube video - Wait Form Dialog.
Edit. I forgot to say that I set my form to 100% opacity so the progress bar seems to float above my winform whenever I use it.
There are definitely resources to help you figure this out, however I spent like 2 days figuring it out so I'm going to help you out here and save you the headache. The progress bar itself is under the main UI thread (like any objects in your form) and needs to be handled by the main thread. Whatever you are trying to do on the side can be handled by a thread like this
String a, b;
a = txtUsername.Text;
b = txtPassword.Password;
Thread Login = new Thread(() => CompleteLogin(a, b));
InversePbVisibility();
Login.Start();
The InversePbVisibility() method would be replaced by whatever you are doing to make the progress bar visible to the user. A quick side note, any methods that are run on your declared thread can only pass variables and not anything already under the control of the main thread.

C# Windows form application is locked during runtime

I'm new to C# and i have a problem ... when i run my windows form application and press start button, during runtime i can't do anything with the form (close, minimize, move, ...) but when it's done i can close the form.
What should i do to solve it?
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication10
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void c1Button1_Click(object sender, EventArgs e)
{
for ( int i = 0; i < 100; i++)
{
c1RadialGauge1.Value = i;
textBox1.Text = i.ToString();
textBox1.Update();
c1Gauge1.Refresh();
Thread.Sleep(100);
}
}
}
}
Since your button handler is running in the UI thread until it is finished, your UI blocks and you can't do anything else with that.
One solution to that would be to make the handler async and use await a Task.Delay() instead of using Thread.Sleep():
private async void c1Button1_Click(object sender, EventArgs e)
{
c1Button1.Enabled = false; // avoid multiple clicks
for ( int i = 0; i < 100; i++)
{
c1RadialGauge1.Value = i;
textBox1.Text = i.ToString();
textBox1.Update();
c1Gauge1.Refresh();
await Task.Delay(100);
}
c1Button1.Enabled = true; // allow further clicks
}
The compiler translates this code into a state machine. At the await keyword, the control flow is returned to the caller (the UI) and resumed when Task.Delay() has finished. So while Task.Delay() runs, the UI thread has time to react on other events (e.g. user interaction).
I added two lines to disable the button while the whole thing is running, so that the user can't start this several times.
But this is only possible using .NET 4.5 or newer. Read this for more information about async/await.
If you have to use an older .NET framework, you may use a BackgroundWorker instead.

Async Ping PingComplete only in Console firing [duplicate]

I am trying to write an application the is constantly searching for host on a lan. When I run this as a console as the countdown.Wait() seems to work fine. However when I bring the code into a windows form the countdown.Signal() does not seem to decrement its counter. Not sure what the problem is.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.NetworkInformation;
using System.Diagnostics;
using System.Net;
using System.Threading;
namespace Multi_Threaded
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
PortScanner ps = new PortScanner();
ps.ProbeCompleted += new PingProbeCompleted(ps_ProbeCompleted);
ps.run_ping_probe();
}
void ps_ProbeCompleted(object sender, PingProbeCompletedArguments e)
{
MessageBox.Show("I found " + e.ip_adresses_list_of_host.Count.ToString() + "host(s)");
}
}
public delegate void PingProbeCompleted(object sender,PingProbeCompletedArguments e);
public class PingProbeCompletedArguments : EventArgs
{
public List<string> ip_adresses_list_of_host;
}
public class PortScanner
{
public event PingProbeCompleted ProbeCompleted;
static List<string> ip_adresses = new List<string>();
static CountdownEvent countdown;
public void run_ping_probe()
{
ip_adresses.Clear();
countdown = new CountdownEvent(1);
string ipBase = "10.125.";
for (int sub = 0; sub < 14; sub++)
{
for (int i = 1; i < 255; i++)
{
string ip = ipBase + sub.ToString() + "." + i.ToString();
Ping p = new Ping();
p.PingCompleted += new PingCompletedEventHandler(p_PingCompleted);
countdown.AddCount();
p.SendAsync(ip, 100, ip);
}
}
countdown.Signal();
countdown.Wait();
PingProbeCompletedArguments e = new PingProbeCompletedArguments();
e.ip_adresses_list_of_host = ip_adresses;
ProbeCompleted(this, e);
}
private void p_PingCompleted(object sender, PingCompletedEventArgs e)
{
string ip = (string)e.UserState;
if (e.Reply.Status == IPStatus.Success)
{
ip_adresses.Add(ip + "\t" + e.Reply.RoundtripTime + " ms");
}
countdown.Signal();
}
}
Yes, your code deadlocks when you use it in a Winforms project. The problem is that the Ping class makes a best effort to raise the PingCompleted event on the same thread that called SendAsync(). It uses the AsyncOperationManager.CreateOperation() method to do so.
Problem is, that actually works in a Winforms app. It tries to raise the event on the main thread. But that cannot work since you blocked the main thread with the countdown.Wait() call. The ping cannot complete since the main thread is blocked. The main thread cannot complete since the ping doesn't complete. Deadlock city.
It works in a Console mode app since it doesn't have a synchronization provider like Winforms does. The PingComplete event will be raised on a threadpool thread.
Blocking the UI thread is fundamentally flawed. The quick fix is to run the code on a worker thread. Beware that this makes the ProbeCompleted event fired on that worker as well. Use Control.BeginInvoke() to marshal it to the UI thread. Or use BackgroundWorker.
private void Form1_Load(object sender, EventArgs e) {
PortScanner ps = new PortScanner();
ps.ProbeCompleted += new PingProbeCompleted(ps_ProbeCompleted);
ThreadPool.QueueUserWorkItem((w) => ps.run_ping_probe());
}
And don't forget to remove the extra Signal() call.
your wait handler is run under a thread from the threadpool.
you need to get back into the UI thread to have the UI updated (due to the message loop that the UI runs on) - for that you use SynchronizationContext
Here more info on how to you it:
http://www.codeproject.com/KB/threads/SynchronizationContext.aspx

C# Threading using invoke, freezing the form

I'm trying to use threads and prevent the program from freezing while the thread is busy. It should show the progress (writing of 0's / 1's) and not just show the result after its done, freezing the form in the meanwhile.
In the current program I'm trying to write to a textbox, and actually see constant progress, and the form can't be affected by the tasks of the other thread.
What I have now is I can write to a textbox with a thread using invoke, but It only shows the result (Form freezes while thread is busy), and the form freezes.
Form image:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace MultiThreading
{
public partial class MultiThreading : Form
{
public MultiThreading()
{
InitializeComponent();
}
Thread writeOne, writeTwo;
private void writeText(TextBox textBox, string text)
{
if (textBox.InvokeRequired)
{
textBox.BeginInvoke((MethodInvoker)delegate()
{
for (int i = 0; i < 500; i++)
{
textBox.Text += text;
}
});
}
else
{
for (int i = 0; i < 500; i++)
{
textBox.Text += text;
}
}
}
private void btnWrite1_Click(object sender, EventArgs e)
{
writeOne = new Thread(() => writeText(txtOutput1, "0"));
writeOne.Start();
}
private void btnWrite2_Click(object sender, EventArgs e)
{
writeTwo = new Thread(() => writeText(txtOutput2, "1"));
writeTwo.Start();
}
private void btnClear1_Click(object sender, EventArgs e)
{
txtOutput1.Clear();
}
private void btnClear2_Click(object sender, EventArgs e)
{
txtOutput2.Clear();
}
private void btnWriteBoth_Click(object sender, EventArgs e)
{
writeOne = new Thread(() => writeText(txtOutput1, "0"));
writeTwo = new Thread(() => writeText(txtOutput2, "1"));
writeOne.Start();
writeTwo.Start();
}
private void btnClearBoth_Click(object sender, EventArgs e)
{
txtOutput1.Clear();
txtOutput2.Clear();
}
}
}
EDIT:
Btw for anyone wondering, I'm new to multithreading and I'm just trying to write a small program to understand the best way to do this.
I understand that my previous invoke didn't realy help because I still wasn't giving the form a chance to update, so its getting there.
Ok so running 1 thread like this works, but still running multiple threads together, won't update the form till after the thread is done.
I've added a thread.sleep() so I can try and clear while writing, to see if I can still use the form.
When writing to 1 textbox I can still clear the screen while writing.
But once I use 2 threads, I can't use the form anymore till the thread completes, and gives the output.
private void writeText(TextBox textBox, string text)
{
for (int i = 0; i < 500; i++)
{
Invoke(new MethodInvoker(() =>
{
textBox.Text += text;
Thread.Sleep(2);
}));
}
}
(If I'm totally wrong on this I don't mind having to read through some examples/threads, I'm still trying to see what is the best way to do this, besides a backgroundworker)
EDIT 2:
I've reduced the number of invokes by reducing the amount to write, but to increase delay giving the same effect of constant writing, just reducing the load.
private void writeText(TextBox textBox, string text)
{
for (int i = 0; i < 500; i++)
{
Invoke(new MethodInvoker(() =>
{
textBox.Text += text;
Thread.Sleep(2);
}));
}
}
EDIT 3:
Sumeet's example works using
Application.DoEvents();
(notice the s, .DoEvent doesn't work, typo probably :P), writing multiple strings simultaneously & having them show the progress and not just the result.
So Code update again :)
*Using a new button to create 5 threads that write a random number to both textboxes
private void writeText(TextBox textBox, string text)
{
for (int i = 0; i < 57; i++)
{
Invoke(new MethodInvoker(() =>
{
textBox.Text += text;
Thread.Sleep(5);
Application.DoEvents();
}));
}
}
private void btnNewThread_Click(object sender, EventArgs e)
{
Random random = new Random();
int[] randomNumber = new int[5];
for (int i = 0; i < 5; i++)
{
randomNumber[i] = random.Next(2, 9);
new Thread(() => writeText(txtOutput1, randomNumber[i-1].ToString())).Start();
new Thread(() => writeText(txtOutput2, randomNumber[i-1].ToString())).Start();
}
}
This solution works ! Have checked it.
The problem is you keep telling the UI thread to change the Text, but never letting it have time to show you the updated text.
To make your UI show the changed text, add the Application.DoEvents line like this :
textBox.Text += text;
Application.DoEvents();
p.s. : Remove the else block of your If / Else loop, it is redundant, and also as pointed by others there is not any use of creating those 2 Threads as all they are doing is post the message on the UI Thread itself.
You're still performing a single-threaded task, just re-launching it on the UI thread if needed.
for (int i = 0; i < 500; i++){
string text = ""+i;
textBox.BeginInvoke((MethodInvoker)delegate()
{
textBox.Text += text;
});
}
The problem is that you're starting a new thread, and then that new thread is doing nothing except adding one new task for the UI thread to process that does a lot of work. To keep your form responsive you need to have time where the UI thread is doing nothing, or at least not spending a significant amount of time doing any one task.
To keep the form responsive we need to have lots of little BeginInvoke (or Invoke) calls.
private void writeText(TextBox textBox, string text)
{
for (int i = 0; i < 500; i++)
{
Invoke(new MethodInvoker(() =>
{
textBox.Text += text;
}));
}
}
By having lots of little invoke calls it allows things like paint events, mouse move/click events, etc. to be handled in the middle of your operations. Also note that I removed the InvokeRequired call. We know that this method will be called from a non-UI thread, so there's no need for it.
You're defeating the purpose of using threads.
All your thread does is tell the UI thread to execute some code using BeginInvoke().
All of the actual work happens on the UI thread.
Either you're doing data processing or you're just trying to animate the UI.
For data processing you should do all the heavy lifting on a background thread and only update the UI occasionally. In your example a TextBox is particularly troublesome in this regard, as you're adding data to the underlying data model several hundred times and the UI element (a TextBox) takes longer to render each time. You must be careful about how often to update the UI so that processing for UI updates does not overwhelm data model updates. TextBoxes are nasty like that.
In the example below, a flag set during the paint event ensures that additional UI updates aren't queued until the TextBox has finished painting the last update:
string str = string.Empty;
public void DoStuff()
{
System.Threading.ThreadPool.QueueUserWorkItem(WorkerThread);
}
void WorkerThread(object unused)
{
for (int i = 0; i < 1000; i++)
{
str += "0";
if (updatedUI)
{
updatedUI = false;
BeginInvoke(new Action<string>(UpdateUI), str);
}
}
BeginInvoke(new Action<string>(UpdateUI), str);
}
private volatile bool updatedUI = true;
void textbox1_Paint(object sender, PaintEventArgs e) // event hooked up in Form constructor
{
updatedUI = true;
}
void UpdateUI(string str)
{
textBox1.Text = str;
}
On the other hand if UI animation is your goal then you probably ought to be using something other than a TextBox. It's just not designed to handle updates so frequently. There might be some optimizations to text rendering you could make for your specific use case.
You must never use a string in high volume applications. UI or not. Multi-threading or not.
You should use StringBuilder to accumulate the string. and then assign
tb.Text = sb.ToString();

Asynchronous code that works in Console but not in Windows Forms

I am trying to write an application the is constantly searching for host on a lan. When I run this as a console as the countdown.Wait() seems to work fine. However when I bring the code into a windows form the countdown.Signal() does not seem to decrement its counter. Not sure what the problem is.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net.NetworkInformation;
using System.Diagnostics;
using System.Net;
using System.Threading;
namespace Multi_Threaded
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
PortScanner ps = new PortScanner();
ps.ProbeCompleted += new PingProbeCompleted(ps_ProbeCompleted);
ps.run_ping_probe();
}
void ps_ProbeCompleted(object sender, PingProbeCompletedArguments e)
{
MessageBox.Show("I found " + e.ip_adresses_list_of_host.Count.ToString() + "host(s)");
}
}
public delegate void PingProbeCompleted(object sender,PingProbeCompletedArguments e);
public class PingProbeCompletedArguments : EventArgs
{
public List<string> ip_adresses_list_of_host;
}
public class PortScanner
{
public event PingProbeCompleted ProbeCompleted;
static List<string> ip_adresses = new List<string>();
static CountdownEvent countdown;
public void run_ping_probe()
{
ip_adresses.Clear();
countdown = new CountdownEvent(1);
string ipBase = "10.125.";
for (int sub = 0; sub < 14; sub++)
{
for (int i = 1; i < 255; i++)
{
string ip = ipBase + sub.ToString() + "." + i.ToString();
Ping p = new Ping();
p.PingCompleted += new PingCompletedEventHandler(p_PingCompleted);
countdown.AddCount();
p.SendAsync(ip, 100, ip);
}
}
countdown.Signal();
countdown.Wait();
PingProbeCompletedArguments e = new PingProbeCompletedArguments();
e.ip_adresses_list_of_host = ip_adresses;
ProbeCompleted(this, e);
}
private void p_PingCompleted(object sender, PingCompletedEventArgs e)
{
string ip = (string)e.UserState;
if (e.Reply.Status == IPStatus.Success)
{
ip_adresses.Add(ip + "\t" + e.Reply.RoundtripTime + " ms");
}
countdown.Signal();
}
}
Yes, your code deadlocks when you use it in a Winforms project. The problem is that the Ping class makes a best effort to raise the PingCompleted event on the same thread that called SendAsync(). It uses the AsyncOperationManager.CreateOperation() method to do so.
Problem is, that actually works in a Winforms app. It tries to raise the event on the main thread. But that cannot work since you blocked the main thread with the countdown.Wait() call. The ping cannot complete since the main thread is blocked. The main thread cannot complete since the ping doesn't complete. Deadlock city.
It works in a Console mode app since it doesn't have a synchronization provider like Winforms does. The PingComplete event will be raised on a threadpool thread.
Blocking the UI thread is fundamentally flawed. The quick fix is to run the code on a worker thread. Beware that this makes the ProbeCompleted event fired on that worker as well. Use Control.BeginInvoke() to marshal it to the UI thread. Or use BackgroundWorker.
private void Form1_Load(object sender, EventArgs e) {
PortScanner ps = new PortScanner();
ps.ProbeCompleted += new PingProbeCompleted(ps_ProbeCompleted);
ThreadPool.QueueUserWorkItem((w) => ps.run_ping_probe());
}
And don't forget to remove the extra Signal() call.
your wait handler is run under a thread from the threadpool.
you need to get back into the UI thread to have the UI updated (due to the message loop that the UI runs on) - for that you use SynchronizationContext
Here more info on how to you it:
http://www.codeproject.com/KB/threads/SynchronizationContext.aspx

Categories