Immediately update UI when OnClick_Event occurs - c#

Very simple example:
<StackLayout>
<Button Text="{Binding LoginButtonText}" Clicked="Button_Clicked"></Button>
</StackLayout>
Code behind:
public partial class ItemsPage : ContentPage
{
private ViewModels.ItemsViewModel _viewModel;
public ItemsPage()
{
_viewModel = new ItemsViewModel();
BindingContext = _viewModel;
InitializeComponent();
}
private void Button_Clicked(object sender, EventArgs e)
{
this._viewModel.LoginButtonText = "Start" + DateTime.Now.ToString();
// this loop is just for testing purposes. To demonstrate
// that this loop block UI thread
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 1000; j++)
{
string s = new Random(45).NextDouble().ToString();
}
}
this._viewModel.LoginButtonText = "End " + DateTime.Now.ToString();
}
}
I'm using MVVM - INotifyPropertyChanged
public class ItemsViewModel : ObservableObject
{
private string _loginButtonText;
public string LoginButtonText
{
get { return _loginButtonText; }
set { SetProperty(ref _loginButtonText, value); }
}
}
ObservableObject implementation can be seen here: https://codeshare.io/G87N74
When I click on the button after a few seconds (4 or 5) the button text gets value 'End 03/08/2017 08:55:33' (will it depends on the current timestamp of course). The button text Start + DateTime does not appear.
It works if I write this:
private void Button_Clicked(object sender, EventArgs e)
{
this._viewModel.LoginButtonText= "Start" + DateTime.Now.ToString();
// I assume here execution switches threads as it sees Task as a new thread. While waiting for task to finish
// it finished mvvm INotifyPropertyChanged change.Invoke call and updates the button text
await Task.Delay(5000);
this._viewModel.LoginButtonText = "End " + DateTime.Now.ToString();
}
But it's not 100% as we don't know how threads will be scheduled for execution. Is there a simple way to update UI immediately when we hit event method?
There is a method Device.BeginInvokeOnMainThread() but it returns void, therefore it doesn't block UI.

We cannot make changes to UI immediately from background thread. All operations on UI thread from background thread will execute on next UI thread cycle. And if your application is not blocked by some intensive tasks, it will be as close to immediately as we can reach.
If you want to make your first example to work well, put that heavy operations to backgorund task:
this._viewModel.LoginButtonText = "Start" + DateTime.Now.ToString();
//With await Task.Run (creating new thread) on heavy operation this thread is not blocked
//so previous operation is scheduled to UI thread and changes will appear on screen
await Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 1000; j++)
{
string s = new Random(45).NextDouble().ToString();
}
}
});
this._viewModel.LoginButtonText = "End " + DateTime.Now.ToString();
Also here is documentation from Xamarin about threads:
Application user interfaces are always single-threaded, even in multi-threaded devices – there’s only one representation of the screen and any changes to what is displayed need to be coordinated through a single ‘access point’. This prevents multiple threads from trying to update the same pixel at the same time (for example)!

Related

C# - WPF - Updating the UI from another class on another Thread

I have looked around the internet and found a nice solution which I am incorporating into my code below however it doesn't quite do exactly what I want, it works when just calling an update but I want to run a method in another class then let that method call the method that will report back to the UI and just pass some information so this mock up is just changing the button content before the operation is ran.
Using a Dispatcher I can get a UI control to update however I don't just wish to do that I want to perform some functions then have the UI Update.
So there maybe some theory I am not getting, I know the Invoke is a synchronous operation and breaking through the code it does run but the UI doesn't update.
MainWindow
Has a single button with content "CLICK ME"
Code Behind
public partial class MainWindow : Window
{
public static Button windowButton;
public MainWindow()
{
InitializeComponent();
windowButton = btnStart;
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
// Run a Process in another class on a different Thread
ProcessClass processClass = new ProcessClass();
Task processTask = new Task(() =>
{
processClass.DoSomething();
});
processTask.Start();
}
}
}
ProcessClass
class ProcessClass:MainWindow
{
public static void UpdateUI()
{
App.Current.Dispatcher.Invoke(delegate
{
windowButton.Content = "CHANGED CONTENT";
});
}
public void DoSomething()
{
UpdateUI();
int counter = 0;
for(int i = 1; i < 100; i++)
{
counter += i;
Thread.Sleep(100);
}
MessageBox.Show($"Task Completed, answer is {counter}");
}
}
Assuming that ProcessClass is your own code that you can update, change the signiture of DoDomething() to
public async Task DoSomething(IProgress<string> progress)
{
progress.Report("Begin DoSomething()");
var counter = 0;
for(var i = 1; i < 100; i++)
{
counter += i;
await Task.Delay(100).ConfigureAwait(false);
progress.Report($"DoSomething() - i = {i}");
}
progress.Report($"DoSomething() Completed, answer is {counter}");
}
Now your button click handler can be written
private async void btnStart_Click(object sender, RoutedEventArgs e)
{
// usually you would update some other control such as a TextBlock
// for the feedback, rather than the button content
var progress = new Progress<string>(s => btnStart.Content = s);
ProcessClass processClass = new ProcessClass();
await processClass.DoSomething(progress).ConfigureAwait(false);
}

Loop-While within a setup timeout that doesn't affect the other functions

I want to put a function on an endless loop but with a timeout countdown. After each time it perform its function, it should stop for a manually-configured amount of time. If I use Sleep, it freezes everything else. Is there a way that I can do this without affecting the other functions in my project?
private void btn30_Click(object sender, EventArgs e)
{
int hour = DateTime.Now.Hour;
int minute = DateTime.Now.Minute;
int second;
do
{
if (5 + DateTime.Now.Second > 60)
{
second = (DateTime.Now.Second + 5) - 60;
}
else if (5 + DateTime.Now.Second == 60)
{
second = 0;
}
else
{
second = DateTime.Now.Second + 5;
}
if (sc.checkScheduleStarted() == false)
{
sc.Start30(hour, minute, second);
btn30.Text = "5 second waiting";
System.Threading.Thread.Sleep(5000);
}
else
{
sc.Stop();
btn30.Text = "Countdown - 5";
}
} while (loopCycle == true);
}
You're using Task.Sleep() on the main UI thread. Obviously you'll end up freezing the UI.
You should consider running your calculations in background with Task.Run and then make delays with non-blocking thread Task.Delay:
private void btn30_Click(object sender, EventArgs e)
{
Task.Run(async() =>
{
do
{
// Your stuff here...
// Pick one of the two examples below for your case:
// 1. Updates the UI of a WPF application
Application.Current.Dispatcher.Invoke(() =>
{
// Update the UI here...
});
// 2. Updates the UI of a WinForm application
Invoke(new Action(() =>
{
// Update the UI here...
}));
// Make a delay...
await Task.Delay(5000);
} while (true);
});
}
Note that when you want to update the UI from a background thread you have to somehow execute your code that changes the UI in the main thread (the one that created controls you're updating).
I would recommend:
For WPF - Dispatcher.Invoke
For WinForms - Control.Invoke
Don't bother with threading. Just drop a Timer control on the form and subscribe to the tick event. The control will call the event within the UI thread and won't freeze the ui.
public class YourForm {
private int Seconds = 5;
private void btn30_Click(object sender, EventArgs e) {
YourTimer.Start();
}
public void YourTimer_Tick(object sender, EventArgs e)
{
Seconds--;
if (Seconds<= 0) {
YourTimer.Stop();
// Do your stuff
}
}
}
Reference : Timer class
Make your click handler asynchronous, and use Task.Delay instead of Thread.Sleep(). This will allow the handler to release control to the calling context and allow it to schedule other tasks while waiting for the Delay to pass.
private async Task btn30_Click(object sender, EventArgs e)
{
int hour = DateTime.Now.Hour;
int minute = DateTime.Now.Minute;
int second;
do
{
if (5 + DateTime.Now.Second > 60)
{
second = (DateTime.Now.Second + 5) - 60;
}
else if (5 + DateTime.Now.Second == 60)
{
second = 0;
}
else
{
second = DateTime.Now.Second + 5;
}
if (sc.checkScheduleStarted() == false)
{
sc.Start30(hour, minute, second);
btn30.Text = "5 second waiting";
await Task.Delay(5000);
//Rest of code

How can I create a large number of controls without blocking the Main Thread?

Hi,guys, I don't understand how to handle the UI thread, so I try to create many UI elements in a child thread, but,
How to modify the following code so that the window don't block:
public void CreateCtr(string name)
{
Button btn = new Button();
btn.Content = name;
btn.Width = 10;
btn.Height = 10;
content.Children.Add(btn);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
int count = 100000;
Task.Factory.StartNew(()=> {
for (int i = 0; i < count; i++)
{
this.Dispatcher.BeginInvoke(new Action(()=> {
CreateCtr(i.ToString());
}));
}
});
}
You need to give the UI thread some space to breathe. With the current code, the UI thread is busy processing all the create button tasks and cannot get to processing user input. Put a minimal pause between creating each individual button. A few milliseconds are enough:
private void Button_Click(object sender, RoutedEventArgs e)
{
int count = 100000;
Task.Factory.StartNew(() => {
for (int i = 0; i < count; i++)
{
this.Dispatcher.BeginInvoke(new Action(() => {
CreateCtr(i.ToString());
}));
Thread.Sleep(5);
}
});
}
The longer the pause, the more responsive the form becomes.
You will note that the form becomes less responsive again after some time, because after a certain amount of components on a form, adding another component takes very long. This is where my original comment becomes relevant: There is no usecase for having that many components on a form, so the framework is not designed to handle it well.
What might work is not putting all buttons on the same parent container but creating a tree of components where each component has no more than say 100 children. But again: I don't see any relevant usecase.
Might work to disable the dispatcher when adding the controls.
int count = 100000;
Task.Factory.StartNew(() => {
using (var d = Dispatcher.DisableProcessing())
{
for (int i = 0; i < count; i++)
{
this.Dispatcher.BeginInvoke(new Action(()=> {
CreateCtr(i.ToString());
}));
}
}
});

Why code after Await does not run on UI thread?

I have used delegates in the past to update the current UI (for example a textbox) with a value that is being processed in my thread. I was under the assumption that async and await took care of this for you.
When I run the code below -when the button is pressed start a new task that will count up to 100 every 100ms. The count should be displayed on the UI, but I get a targetInvocationException instead:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
textBox1.Text = "Press button";
}
private async void button1_Click(object sender, EventArgs e)
{
await Task1();
}
private Task Task1()
{
return Task.Factory.StartNew(() => CountMethod());
}
private void CountMethod()
{
for (int i = 0; i < 100; i++)
{
Task.Delay(100).Wait();
//Console.WriteLine(i.ToString());
textBox1.Text = i.ToString();
}
}
}
The problem is that you are using a thread-pool thread to access the UI. You are doing this by using the Task.Factory.StartNew method.
Here is how you can fix it:
private async Task Task1()
{
for (int i = 0; i < 100; i++)
{
await Task.Delay(100);
textBox1.Text = i.ToString();
}
}
await will capture the current SynchronizationContext so that after the asynchronous wait is complete, the code that follows executes on the UI thread. This is true because await was called by the UI thread (in the case of my example).
In your current code, await is called on a thread-pool thread. In this case the current SynchronizationContext is null and therefore the code that follows the asynchronous wait will be executed also on a thread-pool thread.

How to handle cross thread error in C sharp windows application?

I'm having an application with a progress bar and a buttom.
When the button clicked the progress bar value will get increased, here is the source code,
private void Run()
{
progressBar1.Maximum = 1000;
progressBar1.Minimum = 0;
progressBar1.Step = 1;
for (int l_nIndex = 0; l_nIndex < 1000; l_nIndex++)
{
progressBar1.Value++;
Thread.Sleep(10);
}
}
private void button1_Click(object sender, EventArgs e)
{
Run();
}
so when i run the application, the progress bar value is getting increased, but when i try to move the window its not responding.
I can not run it in a normay thread way - it will throw Cross-Thread error.
so i changed the code like,
private void Run()
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(this.Run));
}
else
{
progressBar1.Maximum = 1000;
progressBar1.Minimum = 0;
progressBar1.Step = 1;
for (int l_nIndex = 0; l_nIndex < 1000; l_nIndex++)
{
progressBar1.Value++;
Thread.Sleep(10);
}
}
}
private void button1_Click(object sender, EventArgs e)
{
Thread myThread = new Thread(new ThreadStart( Run));
myThread.Start();
}
Now i can able to move the winodow, but when i move the progress bar is stopped, and when i release the mouse button its resuming. So still the execution is in UI Thread.
How to handle it in a better way.Please help me to do this .
Invoke() works by running the given delegate from the UI thread. So if you use Invoke() to run your entire method, then your entire method runs from the UI thread.
Instead, you should be doing your actual work in the other thread, and just performing UI updates in the UI thread, by just Invoke()ing the little bits of code that perform the updates.
One easy way to do this is to use the BackgroundWorker class built into the standard library.
This has been answered here - in your case the code should look something like:
this.BeginInvoke((Action)(() => progressBar1.Value++));

Categories