I have a simple login function in my app. When login starts, a boolean is set to true to activate a loading animation, and upon completion it is set to false to hide it. The problem is the UI does not react to the second part, but does react to first.
This is the function:
private async void AttemptLogin()
{
//This updates
LoginInProgress = true;
try
{
var result = await _loginPageViewModel.Login(Username, Password, exception => { Console.WriteLine(exception.Message); }); //TODO add notifier
if (result)
{
Console.WriteLine("success");
_navigationManager.NavigateTo("./");
}
else
{
Console.WriteLine("Failure");
}
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
finally
{
//This does not update
LoginInProgress = false;
}
Console.WriteLine("Login attempt finished");
}
I am using blazorise as a component framework, this is how the value is bound (+ an extra testing checkbox, checking and unchecking updates everything as expected):
<Button Clicked="AttemptLogin" #bind-Loading="LoginInProgress">Login</Button>
<Check TValue="bool" #bind-Checked="LoginInProgress">Test</Check>
For now, StateHasChanged at the LoginInProgress = false; seems to work, but I do not like that I have to put it at some places and not others. What am I doing wrong?
Blazor will perform an internal "StateHasChanged" re-render of current component upon the completion of any event handlers in the component.
So, under normal circumstances having caught the "Clicked" event and handled it in "AttemptLogin" method then Blazor engine would re-render after AttemptLogin has completed and take the variable "LoginInProgress = false" into account. No need to manually call "StateHasChanged.
However, as your method is Async, then the engine will wait for completion. Normally, you'd return a Task that the engine will hold onto and wait until completion of that Task before performing the re-rendering.
In your case, you're returning a void instead of a Task. The result is that as soon as your AttempLogin method calls it first await, then control is returned to the caller (the Blazor engine) and it will consider the method complete.
var result = await _loginPageViewModel
.Login(Username, Password, exception =>
{ Console.WriteLine(exception.Message); });
If you'd returned a Task, it would wait until completion of the method (a result is provided by the Task), but because you return a void, then engine expects no further response from the method and starts re-rendering immediately. This re-rendering will happen before this line is executed:
LoginInProgress = false;
Hence, the re-rendering will be applied whilst LoginInProgress is still true.
Change your return type to Task and the re-rendering will be delayed until the event handler is complete.
Related
I'm trying to chain tasks, so as soon as the one finishes the next starts, but the UI doesn't update. I did a course in react and the one lesson is where you update the UI based on state changes in the application, and that is what I'm trying to replicate. Change the state of the application (basically I'll be running methods that run return a bool for validation), and then update the UI accordingly, I'm also using binding, but for some reason its not running as intended, I don't know if I follow the documentation incorrectly. What can I change or fix to make this work and is it practically correct to use more than one task in a single async Task<T> method
public async Task<string> Connect_To_Ip()
{
await Task.Run(() =>
{
details.State = "Connection To IP 127.0.01.258.....";
Task.Delay(5000).Wait();
}).ContinueWith(result => new Task(async () =>
{
await Task.Run(() =>
{
if (result.Status == TaskStatus.RanToCompletion)
{
details.State = "Validating Card Number......";
}
});
}), TaskContinuationOptions.OnlyOnRanToCompletion);
return details.State;
}
How I'm calling the original task
Task connect = Connect_To_Ip();
await connect;
When you use await then you don't need Task.ContinueWith. Everything that follows the awaited operation is a continuation. Since you want to validate on a background thread, you must post the changes back to the UI thread in order to update the UI elements, otherwise you will produce cross-thread exceptions.
This is because UI elements can't be updated from a background thread, except the update occurs via INotifyPropertyChanged and data binding.
One way to do this is to use the Dispatcher to invoke UI manipulations on the UI thread or use the Progress<T> class, which will always execute the registered callback on the UI thread.
Your fixed and simplified code could look like this example:
public async Task ValidateAsync()
{
// Register the callback that updates the UI with the 'progressReporter'.
// Progress<T> must be instantiated on the UI thread it is associated with
var progressReporter = new Progress<string>(message => details.State = message);
// Execute the operation on a background thread
await Task.Run(() => ConnectToIp(progressReporter));
// Continuation starts here, after await
}
public async Task ConnectToIp(IProgress<string> progressReporter)
{
progressReporter.Report("Connection To IP 127.0.01.258.....");
await Task.Delay(TimeSpan.FromSeconds(5));
// Continuation starts here, after await
progressReporter.Report("Validating Card Number......");
}
It is recommended to use async APIs when possible instead of using background threads. For example, to connect to a server without blocking the UI you can use
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("http://www.contoso.com/");
Many IO classes provide an async API.
Furthermore, I recommend to take a look at the INotifyDataErrorInfo interface. It is the recommended way to implement property validation and allows to provide UI error feedback in a very easy way.
I did this in Windows Forms (I had a test Windows Forms project open), but it should be about the same in WPF. I dropped a button, a label and a text box on the form. Then I wrote this code:
private async void button1_Click(object sender, EventArgs e)
{
var result = await ValidateTextBox();
if (result != null)
{
label1.Text = result;
return;
}
var intResult = await ReadTextBox();
label1.Text = intResult.ToString();
await IncrementTextBox();
intResult = await ReadTextBox();
label1.Text = intResult.ToString();
}
private async Task<string> ValidateTextBox()
{
await Task.Delay(2000);
if (!int.TryParse(textBox1.Text, out _)) {
return "Not Valid";
}
//otherwise
return null;
}
private async Task<int> ReadTextBox()
{
await Task.Delay(3000);
if (!int.TryParse(textBox1.Text, out var result))
{
throw new Exception("Don't do that");
}
return result;
}
private async Task IncrementTextBox()
{
await Task.Delay(3000);
if (!int.TryParse(textBox1.Text, out var result))
{
throw new Exception("Don't do that");
}
textBox1.Text = (result + 1).ToString();
}
If you type something that's not an int into the text box and press the button, a few seconds go by, and then Not Valid shows up in the label.
If there is a number there, then there is a pause and the number shows up in the label. Then another pause and the text box number will increment by 1. Finally after another pause, the label will show the incremented value.
Note that this all runs on a single thread. But, in spite of all the delays, the UI remains responsive the whole time.
Put breakpoints at the start of each function and on the lines after each of the awaits in the button click handler. Step through (not into) the whole thing and you'll see how the awaits create continuations
Supposed I have to get login information from the server then login, then after the login do something next. The goal is to encapsulate all of this into something that caller can wait.
However each of Firebase C# API call already returned separate tasks (that has already been started). My approach is to think of how to chain each of these separate task together.
I am thinking of making a new Task that represent this since it also seems great for aggregating all the exceptions for the caller to catch.
We have
Task<DataSnapshot> getKeyTask = Database.Child("keyLocation").GetValueAsync();
GetValueAsync as I understand already spun up the task. We can observe the returned task or better use continuation to begin something when it finished.
Task loginTask = Auth.SignInWithEmailAndPasswordAsync(username, password);
Auth.SignInWithEmailAndPasswordAsync also spun up a new task in itself. I can also use continuation to wait and begin something else.
After the login I would like to use another database call to get user data.
So
public Task Login()
{
Task<DataSnapshot> getKeyTask = Database.Child("keyLocation").GetValueAsync();
Task loginAfterGetKeyTask = getKeyTask.ContinueWith(gkt =>
{
Task loginTask = Auth.SignInWithEmailAndPasswordAsync(gkt.Result.___.username, gkt.Result.____.password);
loginTask.ContinueWith(lt => { ....the next task.... });
});
return loginAfterGetKeyTask;
}
There are 2 problems I found :
As it goes on the code is getting deeper in the pyramid. Is there any way I can do to make the code looks more sequential?
This Login() function I want to return a Task so that caller can wait for the whole login process to finish then continue, etc. But currently the returned loginAfterGetKey task finished even if there are more things to do inside. I could keep doing .ContinueWith chaining on the outside if not for that there are one more Firebase task that is executed. Ideally I would like to return the Task that represent the whole thing but they are nested inside. Firebase's method start those task on it's own so I think I could not try to bundle all the task on my own.
I tried encapsulate all the tasks as a new Task start from Task.Factory.StartNew however "GetComponent can only be called from main thread" is a problem. (The Auth and other Firebase service would require GetComponent)
I tried using IEnumerator as a return of Login() and planned to put yield in this function, however, "The yield statement cannot be used inside anonymous method or lambda expression".
I tried using loginTask.Wait() inside the first ContinueWith in order to make loginAfterGetKeyTask that I return from this function not arrive at IsCompleted state until it reacehs the end (which would have to wait for the tasks inside) but since the lambda in this ContinueWith is in the main thread, it causes deadlock.
In the end I used IEnumerator loop waiting as a kind of hack to wait for the Task to finish. It 'linearize' the Task multi-thread system into single thread coroutine system that Unity seems to prefer. Each yield return null results in resuming in the next frame to check on the Task's status again.
Not very elegant but I could not find any better way outside async/await that pairs with Task but Unity's C# version does not yet support it.
public void Caller()
{
yield return StartCoroutine(Login());
// Login finished, check the login credentials
}
private IEnumerator WaitTask(Task task)
{
while (task.IsCompleted == false)
{
yield return null;
}
if(task.IsFaulted)
{
throw task.Exception;
}
}
public IEnumerator Login()
{
Task<DataSnapshot> getKeyTask = Database.Child("keyLocation").GetValueAsync();
yield return WaitTask(getKeyTask);
Task loginTask = Auth.SignInWithEmailAndPasswordAsync(getKeyTask.Result.___.username, getKeyTask.Result.____.password);
yield return WaitTask(loginTask);
//... the next task can use loginTask.Result etc.
}
It would be tricky still to catch exception since compiler does not allow try catch to wrap the yield. One way I might use the callback pattern to send the exception to the caller.
Here's an extension version in case you want to do like yield return task.YieldWait(); instead.
public static class TaskExtension
{
/// <summary>
/// Firebase Task might not play well with Unity's Coroutine workflow. You can now yield on the task with this.
/// </summary>
public static IEnumerator YieldWait(this Task task)
{
while (task.IsCompleted == false)
{
yield return null;
}
if(task.IsFaulted)
{
throw task.Exception;
}
}
}
I have a Task that checks the time of a device per second:
public void checkTimeStart()
{
CancellationTokenSource cTokenSource = new CancellationTokenSource();
CancellationToken ct = cTokenSource.Token;
Task task = Task.Factory.StartNew(async () =>
{
ct.ThrowIfCancellationRequested();
while (!ct.IsCancellationRequested)
{
await Task.Delay(1000);
string time = getTime();
TimeUpdateEvent.Invoke(this, new TimeUpdateEventArgs(time));
}
}, cTokenSource.Token);
}
This works perfect if I remove the TimeUpdateEvent.Invoke(this, new TimeUpdateEventArgs(time));. But when I try to invoke the event the task stops completely and it never enters the while loop! I need this event to update time text box whenever I recieve the new time from the device.
I know its possible to update ui directly from anonymous task but this method is implemented in a portable class library. I need it to be platform independent. So every user could update its own ui when they receive TimeUpdateEvent. TimeUpdateEvent is a simple custom event.
Please check if there is any subscription to the event "TimeUpdateEvent" before calling the "checkTimeStart()" method. I think you didn't make any subscription, so on invoking that event system halts. If you will put the invocation part of the code in try catch block:
try
{
TimeUpdateEvent.Invoke(this, new TimeUpdateEventArgs(time));
}
catch (Exception ex)
{
throw;
}
you will get a NullReferenceException ....
So please check the subscription to that event.
Wish you all the best !
In short, I want to display a progress indicator, make a REST service call, and hide the indicator.
My progress indicator is bound to a property which when set to true or false, show or hides it. My indicator is a little spinning wheel and I obviously don't want my UI to hang when I make a the web service call, so this is done by calling an Async function.
I found some code earlier on which works great to achieve what I want to do, however there is a but!
Here is the simplified version code I'm using:
try
{
ThreadPool.QueueUserWorkItem(async data =>
{
var dispatcher = Deployment.Current.Dispatcher;
dispatcher.BeginInvoke(() =>
{
this.IsRunning = true;
});
configModel = LoginUser(loginId, password);
dispatcher.BeginInvoke(() =>
{
this.IsRunning = false;
});
});
}
catch (Exception ex)
{
IDialogService dialogService = this.GetService<IDialogService>();
dialogService.Show("Unhandled Exception", ex.Message);
dialogService = null;
}
The problem occurs when my web service fails for whatever reason i.e. blocked by firewall for example. Even though I have a try catch, it doesn't get caught and crashes my app.
Is there another way to achieve this without using the threadpool? What's the cleanest way to achieve this?
I really need to call the this.IsRunning as this looks after showing and hiding my progress indicator but this obviously needs to be running on the UI thread, while the rest call needs to be running on another thread.
You need to try/catch the actual service call, not the QueueUserWorkItem:
try
{
configModel = LoginUser(loginId, password);
}
catch (...)
{
}
dispatcher.BeginInvoke(() =>
{
this.IsRunning = false;
});
Otherwise, your IsRunning = false will never execute.
However
If you want the cleanest way, then you make LoginUser asynchronous so you can await it.
this.IsRunning = true;
try
{
configModel = await LoginUser(loginId, password);
}
catch (...)
{
// report error here
}
this.IsRunning = false;
In Asp.Net MVC project I have an Asynchronus controller, wand I also have an action of type Task to implement Asynchronus functionality.
Well I have the following code,
Action startMethod = delegate() { CreateUserFunctionality(user.UserID); };
return Task.Factory.StartNew(startMethod).ContinueWith(
t =>
{
return (ActionResult)RedirectToAction("Profile", "UserProfile");
});
And I also has, CreateUserFunctionality as
public void CreateUserFunctionality(int UsrId)
{
if (UsrId !=0)
{
_userService.NewFunctionality(UsrId);
}
}
I expect that the function will start the job, CreateUserFunctionality and returns the view for me which is written in ContinueWith function. However in my case that continuewith part always waits for the CreateUserFunctionality to complete. My aim was to return the view immediatly and not wait for CreateUserFunctionality to finish, as It should continue in background. Please let me find out what I am missing here.
Ofcourse it will wait because you are using ContinueWith method.If you don't want to wait just start your task, and leave it.Return your ActionResult immediately:
var task = Task.Factory.StartNew(startMethod);
return RedirectToAction("Profile", "UserProfile");
From documentation:
Task.ContinueWith Method : Creates a continuation that executes
asynchronously when the target Task completes.
So it is working as expected.