Updating the UI with Xamarin - c#

I am having difficulty updating the UI using Xamarin. The objective is to make reactive UI so the user knows that application is thinking. Below are my attempts.
Attempt 1
private void BeginProcess(string fileName)
{
Device.BeginInvokeOnMainThread(() => {
iContent.Text = "UI Successfully updated.";
});
Device.BeginInvokeOnMainThread(() => {
ProccessFolder testMethod = new ProccessFolder.Initialize(fileName);
});
}
Attempt 2
private void UpdateUI () {
iContent.Text = "UI Successfully updated.";
}
private void BeginProcess(string fileName)
{
System.Threading.Thread t = new System.Threading.Thread(UpdateUI);
t.Priority = System.Threading.ThreadPriority.Highest;
t.IsBackground = false;
t.Start();
Device.BeginInvokeOnMainThread(() => {
ProccessFolder testMethod = new ProccessFolder.Initialize(fileName);
});
}
Attempt 3
private void BeginProcess(string fileName)
{
Device.BeginInvokeOnMainThread(() => {
iContent.Text = "UI Successfully updated.";
});
Task.Delay(5000);
Device.BeginInvokeOnMainThread(() => {
ProccessFolder testMethod = new ProccessFolder.Initialize(fileName);
});
}
Unfortunately none of these work. What does work is if I place the ProcessFolder method in a background thread and invoke the UI changes on the main thread. However the completion time of the ProcessFolder method is slower.
Any suggestion on how I can update the UI while still executing ProcessFolder on the main thread?

Sometimes when your try update something on the main ui from within a method, depending on the way you've written (and/or structured) it can mean that the main dispatcher waits for the method to complete before updating the main ui.
If you were to try the following example it would successfully complete a UI update after each foreach iteration because when it completes it's initial task it wants to return out of the for loop and hits our main thread invoke, which HAS to complete before the loop can iterate again.
private void BeginProcess()
{
Task.Run(()=>{
for (int i = 0; i < 100; i++)
{
// Perform a task
BeginInvokeOnMainThread(() => {
iContent.Text = "UI Successfully updated: " + i " times out of 100";
});
}
})
}
You can sometimes replicate this kind of effect by using:
NSRunLoop.Current.RunUntil(1000);
to allow the UI to catch up when you call the 'BeginInvokeOnMainThread' delegate.

First of all, all the UI updates must be done in the main thread.
For your particular problem maybe you could use async/await (https://msdn.microsoft.com/library/hh191443(vs.110).aspx)
You could do something like in the main thread:
ProccessFolder testMethod = await ProccessFolder.Initialize(fileName);
iContent.Text = "UI Successfully updated.";
You have to make the Initialize method async and to return a task

Related

C# - How to send update value back to form class from a class where the work is done in a different thread

I'm working on a Splash/Loading screen for an app and I'd like to have a progress bar as well as a label on it which will notify the user of current process, e.g. connecting to the database, loading user settings, retrieving x data, retrieving y data.
I only did a tiny bit of BackGroundWorker stuff in VB before and nothing in C# so a bit confused where to start as it looks quite different in C#.
I'd like to keep the form class simple as to only calling specific methods from different classes on a different thread.
I mainly need to know how to update the GUI from the class that does the work as I think I could work out the threading itself from the below code:
using System;
using System.Threading;
public class ThreadWork
{
public static void DoWork()
{
for(int i = 0; i<3;i++) {
Console.WriteLine("Working thread...");
Thread.Sleep(100);
}
}
}
class ThreadTest
{
public static void Main()
{
Thread thread1 = new Thread(ThreadWork.DoWork);
thread1.Start();
for (int i = 0; i<3; i++) {
Console.WriteLine("In main.");
Thread.Sleep(100);
}
}
}
You could use the Task.Run method to offload work to a ThreadPool thread, and a Progress<string> object to report progress from the background thread to the UI. You can also use async/await in order to write your code in a straightforward way. Here is an example:
private async void Form_Load(object sender, EventArgs e)
{
IProgress<string> progress = new Progress<string>(message =>
{
Label1.Text = message;
});
var result = await Task.Run(() =>
{
progress.Report("Connecting to database...");
ConnectToDatabase();
progress.Report("Loading user settings...");
LoadUserSettings();
progress.Report("Retrieving x data...");
return RetrieveXData();
});
// At this point the background processing has completed
Label1.Text = $"Done! ({result})";
}
If you have heterogeneous data to report at different intervals, you can use multiple Progress<T> objects (for example a Progress<string>, a Progress<int> etc). You can also use a complex type for reporting, for example Progress<(string, int)>.
Although Mitch's answer will work. You can also use TaskSchedulers to handle background work and then respond back to UI:
public static void Main()
{
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => DoBackgroundWork(uiScheduler));
}
private static void DoBackgroundWork(TaskScheduler uiScheduler)
{
// Do background stuff here...
Task.Factory.StartNew(() =>
{
// Respond back to UI here
Console.WriteLine("Doing work");
// Or if you have to change GUI, let's say a label value;
label1.Text = "Text changed by background Task";
}, CancellationToken.None, TaskCreationOptions.None, uiScheduler);
// Some other background work
}

async/await running on single thread

I have a console application where in some instances a user interface needs to be presented. This user interface needs to remain responsive as it will contain a loading gif, progress bar, cancel button etc. I have the following sample code:
class Program
{
static void Main(string[] args)
{
DoWork().GetAwaiter().GetResult();
}
private static async Task DoWork()
{
TestForm form = new TestForm();
form.Show();
string s = await Task.Run(() =>
{
System.Threading.Thread.Sleep(5000);
return "Plop";
});
if (s == "Plop")
{
form.Close();
}
}
}
I would expect from the code above for the TestForm to be displayed for approximately 5 seconds before being closed due to the value of the string being "Plop", however all that happens is the Task is run and the if statement is never reached. Furthermore the UI of the TestForm does not remain responsive. What is wrong with this code?
So I've managed to hack together a dirty solution for this. It is not a clean solution so I'm still open to suggestions but for what I need it works fine
private static void DoWork()
{
TestForm form = new TestForm();
Task formTask = Task.Run(() => form.ShowDialog());
Task<string> testTask = Task.Run(() =>
{
for (int i = 1; i < 10; i++)
{
Thread.Sleep(1000);
Console.WriteLine(i.ToString());
}
Console.WriteLine("Background task finished");
return "Plop";
});
Console.WriteLine("Waiting for background task");
testTask.Wait();
if (testTask.Result == "Plop")
{
Dispatcher.CurrentDispatcher.InvokeAsync(() => form.Close());
}
Console.WriteLine("App finished");
}
This outputs 'Waiting for background task' first, followed by the number count of the Task and then outputs 'Background task finished' when the long process is complete, as well as closes the responsive UI form
Its a classic deadlock.When your code hit await ,control goes back to main thread which is a blocking wait for DoWork GetResult(); When Task.Run thread is finished controls tries to go back to main thread but its waiting for DoWork to be finished. That is the reason last If statement never executes.
But apart from deadlock ,there is also one more issue in your code which will make your UI freeze.Its the form.Show() method.If you remove everything related to async-await and only use form ,it will still freeze.The problem is Show method expects a windows message loop which will be provided if you create a Windows.Forms application but here you are launching form from console application which doesnt have a message loop. One solution would be to use form.ShowDialog which will create its own message loop. Another solution is to use System.Windows.Forms.Application.Run method which provides a win messages loop to the form created through thread pool thread. I can give you one possible solution here but its up to you how you structure your code as the root cause is identified.
static void Main(string[] args)
{
TestForm form = new TestForm();
form.Load += Form_Load;
Application.Run(form);
}
private static async void Form_Load(object sender, EventArgs e)
{
var form = sender as Form;
string s = await Task.Run(() =>
{
System.Threading.Thread.Sleep(5000);
return "Plop";
});
if (s == "Plop")
{
form?.Close();
}
}
Ok I did mark my first answer to be deleted, since what I put there works for WPF and not for you require, BUT in this one is doing what you asked, I did try it and opens the WinForm then closes after 5 seconds, here is the code:
static void Main(string[] args)
{
MethodToRun();
}
private static async void MethodToRun()
{
var windowToOpen = new TestForm();
var stringValue = String.Empty;
Task.Run(new Action(() =>
{
Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
windowToOpen.Show();
}).Wait();
System.Threading.Thread.Sleep(5000);
stringValue = "Plop";
Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
if (String.Equals(stringValue, "Plop"))
{
windowToOpen.Close();
}
}).Wait();
})).Wait();
}

Main process gets frozen after starting another thread

I am trying to write an application which transfers data between 2 systems. This application is used by a user, so it is WinForm application. When data transfering is started by a click of the user, the GUI gets frozen even though I start the data transfering in another thread. I am doing something wrong but I couldnt figure it out. here is my SIMPLIFIED code below....
What am I doing wrong?
// Button Click Event
private void btnStart_Click(object sender, EventArgs e)
{
StartThread();
}
// This starts the threaad.
public static void StartThread()
{
string msg = string.Empty;
int i = 0;
continue_ = true;
if (list != null)
{
while (continue_)
{
i++;
Thread.Sleep(5000);
Thread thrd1 = new System.Threading.Thread(() => Test());
thrd1.Start();
}
}
}
// This is a simplified code.
public static void Test()
{
string msg = string.Empty;
int i = 0;
continue_ = true;
while (continue_)
{
i++;
Thread.Sleep(5000);
FormMain.dal.ExecuteQuery("INSERT INTO A_TEST VALUES('"+i+"')",null,CommandType.Text,out msg);
}
}
Your StartThread() method includes a Thread.Sleep(5000) ... this is happening in your button click method, thus is making the UI thread sleep. Also, it looks like you have an infinite loop on the UI thread as continue_ never gets set to false
I'm guessing what you're trying to achieve here, but this may help:
public static void StartThread()
{
Thread thrd1 = new System.Threading.Thread(() => Test());
thrd1.Start();
}
Let's have a look at this block in StartThread:
while (continue_)
{
i++;
Thread.Sleep(5000);
Thread thrd1 = new System.Threading.Thread(() => Test());
thrd1.Start();
}
You have a while loop dependen on continue_, but you never change it to false. So you get first of all an infinite loop, which causes the GUI to freeze.
why you are modifying i, but never using it, so just remove it.
You don't need also Thread.Sleep(5000);. However, if you really want to wait a time period, you can use an async delay. It will give the GUI free, so that the GUI works until the delay is finished. But for this, you have to declare StartThread as async.
In your:
if (list != null)
{
while (continue_)
{
i++;
Thread.Sleep(5000);
Thread thrd1 = new System.Threading.Thread(() => Test());
thrd1.Start();
}
}
You use Thread.Sleep(5000);
This however still targets your main thread.
I would suggest you to remove this line.
Also, why do you use the variable 'i' while you never use it?

How to run codes on background thread in Windows Runtime

I'm using incremental loading to show a ListView items. I run LoadDetails method in the background thread using Task.Run(...) to not busy the UI thread.
But it still blocks the UI thread and it doesn't render UI elements until it finishes the task.
executing LoadDetails method takes around 3 seconds to complete.
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
await Task.Run(() => LoadDetails(textBlock, item.Id));
}
private async Task LoadDetails(TextBlock textBlock, string id)
{
int count = await DataSource.GetItemCounts(id);
await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
textBlock.Text = count.ToString();
});
}
How to fix this so it doesn't block the UI thread? thanks.
(It's a Windows Phone Runtime app)
It's not clear from your question how you are measuring the 3 second delay. Is it that the call to GetItemCounts() itself takes 3 seconds? If so, isn't that to be expected? The delay is why you would execute that asynchronously in the first place, isn't it?
The code you posted doesn't really seem quite right. Since your new Task doesn't await the call to LoadDetails(), that task will finish right away, without any synchronization with the actual work being done. Written differently, you could also avoid having to call through the Dispatcher directly.
I would have written it something more like this:
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
await LoadDetails(textBlock, item.Id);
}
private async Task LoadDetails(TextBlock textBlock, string id)
{
int count = await DataSource.GetItemCounts(id);
textBlock.Text = count.ToString();
}
I.e. as long as you keep awaiting on the UI thread, you don't need to invoke via the Dispatcher. Note that the above assumes you need the LoadDetails() method, presumably because you call it from multiple places and some require this particular implementation for some reason. But note that you could have just written the LoadItemCounts() method like this, and left out the LoadDetails() method altogether:
private async void LoadItemCounts(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase != 6)
{
throw new Exception("Not in phase 6");
}
var item = args.Item as ItemModel;
var templateRoot = (Grid)args.ItemContainer.ContentTemplateRoot;
var textBlock = (TextBlock)templateRoot.FindName("textBlock");
textBlock.Text = (await DataSource.GetItemCounts(id)).ToString();
}
It looks like your code is correctly not blocking the UI thread by using await, but since LoadItemDetails() is presumably being called on the UI thread, it won't finish until the method is finished doing its work.
To fix this, just omit the await on the call to Task.Run(), so something like
Task.Run(() => LoadDetails(textBlock, item.Id));
should make LoadItemDetails() return immediately.

c# thread and a waiting form

I connect to a webserive. While the webservice is connected i want to have a waiting form with an animated gif inside of it. The waiting form is correctly displayed but the animated give is not animated it is fixed.
Can anybody help me. I have already tried : DoEvents but the gif is still not animated.
// Create the new thread object
Thread NewThread = new Thread(new ThreadStart(RunThread));
// Start the new thread.
NewThread.Start();
// Inform everybody that the main thread is waiting
FRM_Wait waitingDialog = new FRM_Wait();
waitingDialog.Show();
waitingDialog.Activate();
Application.DoEvents();
// Wait for NewThread to terminate.
NewThread.Join();
// And it's done.
waitingDialog.Close();
MessageBox.Show("Upload erfolgreich erledigt.", "Upload Erfolgreich",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
public void RunThread()
{
mfsportservicedev.ServiceSoapClient servicedev = new mfsportservicedev.ServiceSoapClient();
int status = servicedev.addEvent(videosNames, videos);
}
Don't call Join on a thread from within the UI thread. Instead, disable any controls you don't want to act on until the task has completed (e.g. buttons) and then call back into the UI thread when the operation has completed - so move the "And it's done" code into a new method which is invoked at the end of the operation. If you're using .NET 4, I'd suggest using the TPL for this, as it makes it easier to represent "a task which is in progress" and to add a continuation to it. (It's also a good start for what will become the idiomatic way of doing async operations in .NET 4.5.)
The problem is coming from your join. join is synchronous, so basically you are making your UI wait till the thread finishes its work.
You want to use a callback function to come back to your UI.
Edit : ive been skeetified
You problem is here:
NewThread.Join();
This blocks the UI thread until NewThread ends.
Here's one way to do it:
private myDelegate;
// ...
myDelegate = new Action(RunThread);
myDelegate.BeginInvoke(new AsyncCallback(MyCallback),null);
// You RunThread method is now running on a separate thread
// Open your wait form here
// ...
// This callback function will be called when you delegate ends
private void MyCallback(IAsyncResult ar)
{
myDelegate.EndInvoke(ar);
// Note this is still not the UI thread, so if you want to do something with the UI you'll need to do it on the UI thread.
// using either Control.Invoke (for WinForms) or Dispatcher.Invoke (for WPF)
}
Thread.Join is a blocking call that does not pump messages so that is your problem. It is typically advised to avoid calling any kind of synchronization mechanism that causes the UI thread to block.
Here is a solution using the Task class and the Invoke marshaling technique.
private void async InitiateWebService_Click(object sender, EventArgs args)
{
FRM_Wait waitingDialog = new FRM_Wait();
waitingDialog.Show();
Task.Factory.StartNew(
() =>
{
mfsportservicedev.ServiceSoapClient servicedev = new mfsportservicedev.ServiceSoapClient();
int status = servicedev.addEvent(videosNames, videos);
waitingDialog.Invoke(
(Action)(() =>
{
waitingDialog.Close();
}));
});
}
Here is a solution using a raw Thread.
private void async InitiateWebService_Click(object sender, EventArgs args)
{
FRM_Wait waitingDialog = new FRM_Wait();
waitingDialog.Show();
var thread = new Thread(
() =>
{
mfsportservicedev.ServiceSoapClient servicedev = new mfsportservicedev.ServiceSoapClient();
int status = servicedev.addEvent(videosNames, videos);
waitingDialog.Invoke(
(Action)(() =>
{
waitingDialog.Close();
}));
});
thread.Start();
}
C# 5.0 makes this kind of pattern even easier with its new async and await keywords1.
private void async InitiateWebService_Click(object sender, EventArgs args)
{
FRM_Wait waitingDialog = new FRM_Wait();
waitingDialog.Show();
await Task.Run(
() =>
{
mfsportservicedev.ServiceSoapClient servicedev = new mfsportservicedev.ServiceSoapClient();
int status = servicedev.addEvent(videosNames, videos);
});
waitingDialog.Close();
}
1Not yet released.

Categories

Resources