In my project I want to show a progress indicator, retrieve the data from the webservice and hide the progress indicator. If I do this I currently have to wait until I retrieved the data from the webservice and then the UI with the results is immediately updated. But the progress indicator never appears. So it seems, that I'm working or blocking on the UI thread. But where?
This is a simple version of my code:
SomePage
private async void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
await ShowDetailView();
}
private async Task ShowDetailView()
{
var view = new MyView();
this.detailView = view;
await view.LoadContent();
}
SomeView
public async Task LoadContent()
{
var success = await LoadData();
if (success)
{
ShowInformation();
}
}
public async Task<bool> LoadData()
{
try
{
ShowLoadingProcess();
await Task.Delay(5000);
this.itemList = await WebService.Instance.GetData();
return true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
return false;
}
finally
{
HideLoadingProcess();
}
}
private void ShowInformation()
{
Device.BeginInvokeOnMainThread(() =>
{
this.grid.Clear();
foreach(var item in this.itemList)
{
GridItem contentItem = new GridItem(item);
this.grid.Children.Add(contentItem);
}
}
}
private void ShowLoadingProcess()
{
Device.BeginInvokeOnMainThread(() => BringProgressIndicatorToFront());
}
private void HideLoadingProcess()
{
Device.BeginInvokeOnMainThread(() => BringProgressIndicatorToBack());
}
I tried different things, where I got out of sync between the UI and the background thread. E.g. ShowInformation() was called, before LoadData() finished.
Can someone give me a hint about what's wrong here?
I'm not fully sure why your code acts the way it does but you should take into account that Device.BeginInvokeOnMainThread() doesn't give any promises about when the action is executed. Internally it does the following:
// Simplified
s_handler = new Handler(Looper.MainLooper);
s_handler.Post(action);
It passes your action to the message queue which then executes the action at an unknown point of time in the future.
However, what I would suggest you is to rethink the architecture of your application. By taking advantage of the MVVM pattern and data binding you would be able to control the visibility of the progress indicator very easily.
In the ViewModel you would have the following:
private bool isBusy;
public bool IsBusy() {
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
In the View that has the above ViewModel as its BindingContext, you would have something like the following:
<ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}" />
Now, whenever you'd start a long running operation, you'd simply change isBusy to true and the View would automatically show the running ActivityIndicator due to the binding.
I'm not familiar with xamarin.forms, so the following could be utterly rubbish. If so, please do tell me, I'll delete the answer.
To me it seems you have some Form with an event handler OnItemSelected that should Show a DetailView, which is some other form. After creation of the DetailView object you want to load the detail view. This loading takes some time, and in the mean time you want to inform the operator that the form is loading.
I'm not sure where you want the visual feedback: on SomePage or on SomeView. The answer does not really matter, except that the command to show and hide the visual feedback is best written in the class that shows the feedback.
Show the feedback on SomeView
In this case SomeView has something to show and hide the visual feedback, like a progress bar, or an ajax loader. Code would be like:
As I am familiar with Forms, I'll write it as a Form. Your code will be very similar
class SomeView : Form
{
void ShowVisualFeedBack()
{
...
}
void HideVisualFeedBack()
{
...
}
public async Task LoadDataAsync()
{
bool result = false;
this.ShowVisualFeedBack()
try
{
this.itemList = await WebService.Instance.GetData();
result = true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
this.HideVisualFeedBack();
return result;
}
}
Or if you really need to wait 5 seconds before you start getting the data:
try
{
await Task.Delay(TimeSpan.FromSeconds(5));
this.itemList = await WebService.Instance.GetData();
result = true;
}
Show the feedback on SomePage
class SomePage : Form
{
void ShowVisualFeedBack()
{
...
}
void HideVisualFeedBack()
{
...
}
private async Task ShowDetailView()
{
this.ShowVisualFeedBack();
this.DetailView = new MyView();
await view.LoadContent();
this.HideVisualFeedBack();
}
}
By the way, while your thread is awaiting GetData(), it can't update your visual feedback. Some visual feedback methods like GIF don't need updates by your thread, but if you have something like a progress bar, your thread needs to update it. You can do this by awaiting a maximum time until GetData is completed, update the progress, and await again
var taskWaitASec = Task.Delay(TimeSpan.FromSeconds(1));
var taskGetData = WebService.Instance.GetData();
// note: you are not awaiting yet, so you program continues:
while (!taskGetData.IsCompleted)
{
var myTasks = new Task[] {taskWaitASec, taskGetData}
var completedTask = await Task.WhenAny(myTasks);
if (completedTask == taskWaitASec)
{
UpdateProgress();
taskWaitASec = Task.Delay(TimeSpan.FromSeconds(1));
}
}
Very silly mistake. I had something like this:
private async Task ShowDetailView()
{
var view = new MyView();
this.detailView = view;
await view.LoadContent();
this.mainGrid.Children.Add(this.detailView);
}
Of course he waits until the operation finishes and then the results are shown. This is the correct way:
private async Task ShowDetailView()
{
var view = new MyView();
this.detailView = view;
this.mainGrid.Children.Add(this.detailView);
await view.LoadContent();
}
Should I delete my question? Perhaps, because sometimes you miss the forest for the trees.
Related
I'm trying to asynchronously show a progress form that says the application is running while the actual application is running.
As following this question, I have the following:
Main Form:
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
async Task<int> LoadDataAsync()
{
await Task.Delay(2000);
return 42;
}
private async void Run_Click(object sender, EventArgs e)
{
var runningForm = new RunningForm();
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.Close();
await progressFormTask;
MessageBox.Show(data.ToString());
}
}
Progress Form
public partial class RunningForm : Form
{
private readonly SynchronizationContext synchronizationContext;
public RunningForm()
{
InitializeComponent();
synchronizationContext = SynchronizationContext.Current;
}
public async void ShowRunning()
{
this.RunningLabel.Text = "Running";
int dots = 0;
await Task.Run(() =>
{
while (true)
{
UpadateUi($"Running{new string('.', dots)}");
Thread.Sleep(300);
dots = (dots == 3) ? 0 : dots + 1;
}
});
}
public void UpadateUi(string text)
{
synchronizationContext.Post(
new SendOrPostCallback(o =>
{
this.RunningLabel.Text = text;
}),
text);
}
public void CloseThread()
{
synchronizationContext.Post(
new SendOrPostCallback(o =>
{
this.Close();
}),
null);
}
}
internal static class DialogExt
{
public static async Task<DialogResult> ShowDialogAsync(this Form form)
{
await Task.Yield();
if (form.IsDisposed)
{
return DialogResult.OK;
}
return form.ShowDialog();
}
}
The above works fine, but it doesn't work when I'm calling from outside of another from. This is my console app:
class Program
{
static void Main(string[] args)
{
new Test().Run();
Console.ReadLine();
}
}
class Test
{
private RunningForm runningForm;
public async void Run()
{
var runningForm = new RunningForm();
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.CloseThread();
await progressFormTask;
MessageBox.Show(data.ToString());
}
async Task<int> LoadDataAsync()
{
await Task.Delay(2000);
return 42;
}
}
Watching what happens with the debugger, the process gets to await Task.Yield() and never progresses to return form.ShowDialog() and thus you never see the RunningForm. The process then goes to LoadDataAsync() and hangs forever on await Task.Delay(2000).
Why is this happening? Does it have something to do with how Tasks are prioritized (ie: Task.Yield())?
Watching what happens with the debugger, the process gets to await
Task.Yield() and never progresses to return form.ShowDialog() and thus
you never see the RunningForm. The process then goes to
LoadDataAsync() and hangs forever on await Task.Delay(2000).
Why is this happening?
What happens here is that when you do var runningForm = new RunningForm() on a console thread without any synchronization context (System.Threading.SynchronizationContext.Current is null), it implicitly creates an instance of WindowsFormsSynchronizationContext and installs it on the current thread, more on this here.
Then, when you hit await Task.Yield(), the ShowDialogAsync method returns to the caller and the await continuation is posted to that new synchronization context. However, the continuation never gets a chance to be invoked, because the current thread doesn't run a message loop and the posted messages don't get pumped. There isn't a deadlock, but the code after await Task.Yield() is never executed, so the dialog doesn't even get shown. The same is true about await Task.Delay(2000).
I'm more interested in learning why it works for WinForms and not for
Console Applications.
You need a UI thread with a message loop in your console app. Try refactoring your console app like this:
public void Run()
{
var runningForm = new RunningForm();
runningForm.Loaded += async delegate
{
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.Close();
await progressFormTask;
MessageBox.Show(data.ToString());
};
System.Windows.Forms.Application.Run(runningForm);
}
Here, the job of Application.Run is to start a modal message loop (and install WindowsFormsSynchronizationContext on the current thread) then show the form. The runningForm.Loaded async event handler is invoked on that synchronization context, so the logic inside it should work just as expected.
That however makes Test.Run a synchronous method, i. e., it only returns when the form is closed and the message loop has ended. If this is not what you want, you'd have to create a separate thread to run your message loop, something like I do with MessageLoopApartment here.
That said, in a typical WinForms or WPF application you should almost never need a secondary UI thread.
I need to open my popup screen when my method is called, but it is being ignored by my await and does not show the popup.
This is for an android device and I am using rg.plugins.popup to display my popup page and I'm using VS17 as the IDE.
So far I've also tried calling my method synchronously but that doesn't seem to work either.
public async void PegaValor(bool retry)
{
await PopupNavigation.PushAsync(new Paginas.PopupTentarNovamente());
Paginas.PopupTentarNovamente tentarNovamente = new Paginas.PopupTentarNovamente();
if (tentarNovamente.resultado)
{
retry = false;
}
else
{
retry = true;
}
}
I have a suspicion that the pop up needs to be ran on the main UI Dispatcher, I'd suggest trying the following:
public async void PegaValor(bool retry)
{
Activity.RunOnUiThread(async () => {
await PopupNavigation.PushAsync(new Paginas.PopupTentarNovamente());
});
Paginas.PopupTentarNovamente tentarNovamente = new Paginas.PopupTentarNovamente();
if (tentarNovamente.resultado)
{
retry = false;
}
else
{
retry = true;
}
}
EDIT:
Based on your comments and taking a quick look at your github link here are a couple of ways you can do what I suggested, although I still have no clue how your instantiating the class from the git repo.
So for a quick example, this way assumes your using that class inside of an activity, so you could try the following
// Create an instance of metodosEmpenho in your activity and pass through the Activity as a parameter to the constructor.
public class MyActivity : Activity
{
private MetodosEmpenho metodosEmpenho;
protected override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
metodosEmpenho = new MetodosEmpenho(Context as Activity);
metodosEmpenho.VerOperador("");
}
}
// Create a consturctor in the MetodosEmpenho class that will take the Activity paramter and store it for later use.
public class MetodosEmpenho
{
private readonly Activity _activity
public MetodosEmpenho(Activity currActivity)
{
_activity = currActivity;
}
public async void PegaValor(bool retry)
{
_activity.RunOnUiThread(async () => {
await PopupNavigation.PushAsync(new Paginas.PopupTentarNovamente());
});
Paginas.PopupTentarNovamente tentarNovamente = new Paginas.PopupTentarNovamente();
if (tentarNovamente.resultado)
{
retry = false;
}
else
{
retry = true;
}
}
}
In my application i have class PrimaryViewModel (abstract) and every ViewModel inherit from it.
To get data every X seconds i have written following virtual method
protected virtual async void RunSynchronizeTask()
{
await Task.Run(() =>
{
while (true)
{
Thread.Sleep(RefreshTime);
if (DateTime.Now.TimeOfDay - LastSyncrhonization.TimeOfDay > RefreshTime)
{
Application.Current.Dispatcher.Invoke(delegate
{
GetDataAndRefreshUI();
});
LastSyncrhonization = DateTime.Now;
}
}
});
}
So every viewModel has it owns method and it own task which will get data from DB and refresh binded controls (for example messageViewModel refresh messages list every 0.5 seconds)
but problem is that when i run released application (no debug mode, just build as release and open exe) and close, the app will close, but this async task is still working (i see it in task manager)
What should i do?
How to assign this task to something, i cannot do :
var task = await.Task.Run(...)
If i could i would create some static list with referencses to theses tasks and on application close i would kill them or sth...
You go from an async void to a Task.Run() , which is doubling up.
And then you do the actual work in a Dispatcher.Invoke()
The only thing that is truly async (concurrent) here is the Thread.Sleep().
So you can replace all this with a DispatcherTimer. That should cure your halting problem too.
Thanks Fabian!
works great with cancellation token :
protected virtual async void RunSynchronizeTask()
{
var cancelationToken = new CancellationToken();
App.TaskToDisposeTokens.Add(cancelationToken);
await Task.Run(() =>
{
try {
while (true)
{
Thread.Sleep(RefreshTime);
if (DateTime.Now.TimeOfDay - LastSyncrhonization.TimeOfDay > RefreshTime)
{
Application.Current.Dispatcher.Invoke(delegate
{
GetDataAndRefreshUI();
});
LastSyncrhonization = DateTime.Now;
}
}
}
catch(OperationCanceledException) { }
}, cancelationToken);
}
App.Cs
private void Application_Exit(object sender, ExitEventArgs e)
{
foreach(var token in TaskToDisposeTokens)
{
token.ThrowIfCancellationRequested();
}
}
and in App.Xaml
<Application x:Class="RandevouWpfClient.App"
...
Exit="Application_Exit">
I'm trying to asynchronously show a progress form that says the application is running while the actual application is running.
As following this question, I have the following:
Main Form:
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
async Task<int> LoadDataAsync()
{
await Task.Delay(2000);
return 42;
}
private async void Run_Click(object sender, EventArgs e)
{
var runningForm = new RunningForm();
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.Close();
await progressFormTask;
MessageBox.Show(data.ToString());
}
}
Progress Form
public partial class RunningForm : Form
{
private readonly SynchronizationContext synchronizationContext;
public RunningForm()
{
InitializeComponent();
synchronizationContext = SynchronizationContext.Current;
}
public async void ShowRunning()
{
this.RunningLabel.Text = "Running";
int dots = 0;
await Task.Run(() =>
{
while (true)
{
UpadateUi($"Running{new string('.', dots)}");
Thread.Sleep(300);
dots = (dots == 3) ? 0 : dots + 1;
}
});
}
public void UpadateUi(string text)
{
synchronizationContext.Post(
new SendOrPostCallback(o =>
{
this.RunningLabel.Text = text;
}),
text);
}
public void CloseThread()
{
synchronizationContext.Post(
new SendOrPostCallback(o =>
{
this.Close();
}),
null);
}
}
internal static class DialogExt
{
public static async Task<DialogResult> ShowDialogAsync(this Form form)
{
await Task.Yield();
if (form.IsDisposed)
{
return DialogResult.OK;
}
return form.ShowDialog();
}
}
The above works fine, but it doesn't work when I'm calling from outside of another from. This is my console app:
class Program
{
static void Main(string[] args)
{
new Test().Run();
Console.ReadLine();
}
}
class Test
{
private RunningForm runningForm;
public async void Run()
{
var runningForm = new RunningForm();
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.CloseThread();
await progressFormTask;
MessageBox.Show(data.ToString());
}
async Task<int> LoadDataAsync()
{
await Task.Delay(2000);
return 42;
}
}
Watching what happens with the debugger, the process gets to await Task.Yield() and never progresses to return form.ShowDialog() and thus you never see the RunningForm. The process then goes to LoadDataAsync() and hangs forever on await Task.Delay(2000).
Why is this happening? Does it have something to do with how Tasks are prioritized (ie: Task.Yield())?
Watching what happens with the debugger, the process gets to await
Task.Yield() and never progresses to return form.ShowDialog() and thus
you never see the RunningForm. The process then goes to
LoadDataAsync() and hangs forever on await Task.Delay(2000).
Why is this happening?
What happens here is that when you do var runningForm = new RunningForm() on a console thread without any synchronization context (System.Threading.SynchronizationContext.Current is null), it implicitly creates an instance of WindowsFormsSynchronizationContext and installs it on the current thread, more on this here.
Then, when you hit await Task.Yield(), the ShowDialogAsync method returns to the caller and the await continuation is posted to that new synchronization context. However, the continuation never gets a chance to be invoked, because the current thread doesn't run a message loop and the posted messages don't get pumped. There isn't a deadlock, but the code after await Task.Yield() is never executed, so the dialog doesn't even get shown. The same is true about await Task.Delay(2000).
I'm more interested in learning why it works for WinForms and not for
Console Applications.
You need a UI thread with a message loop in your console app. Try refactoring your console app like this:
public void Run()
{
var runningForm = new RunningForm();
runningForm.Loaded += async delegate
{
runningForm.ShowRunning();
var progressFormTask = runningForm.ShowDialogAsync();
var data = await LoadDataAsync();
runningForm.Close();
await progressFormTask;
MessageBox.Show(data.ToString());
};
System.Windows.Forms.Application.Run(runningForm);
}
Here, the job of Application.Run is to start a modal message loop (and install WindowsFormsSynchronizationContext on the current thread) then show the form. The runningForm.Loaded async event handler is invoked on that synchronization context, so the logic inside it should work just as expected.
That however makes Test.Run a synchronous method, i. e., it only returns when the form is closed and the message loop has ended. If this is not what you want, you'd have to create a separate thread to run your message loop, something like I do with MessageLoopApartment here.
That said, in a typical WinForms or WPF application you should almost never need a secondary UI thread.
If await can be used only by async methods, how can I call a task from MainPage()?
My code sample:
public MainPage()
{
InitializeComponent();
label.Text=await Task.Run(TaskTest); //this doesn't work
}
private async Task<string> TaskTest()
{
try
{
using (WebClient client = new WebClient())
{
return await client.DownloadStringTaskAsync("https://www.example.com/return.php");
//also tried w/ no success:
//return client.DownloadStringTaskAsync("https://www.example.com/return.php").Result;
}
}
catch (Exception)
{
throw;
}
}
Avoid async void fire-and-forget methods.
Event handlers however are the only exception to that rule.
Reference Async/Await - Best Practices in Asynchronous Programming
In this case, since you want to await the task then create and event and handler that would facilitate the desired behavior
public MainPage() {
InitializeComponent();
Downloading += OnDownloading; //subscribe to event
Downloading(this, EventArgs.Empty); //raise event to be handled
}
private event EventHandler Downloading = delegate { };
private async void OnDownloading(object sender, EventArgs args) {
//Downloading -= OnDownloading; //unsubscribe (optional)
label.Text = await TaskTest(); //this works
}
private async Task<string> TaskTest() {
try {
using (WebClient client = new WebClient()) {
return await client.DownloadStringTaskAsync("https://www.example.com/return.php");
}
} catch (Exception) {
throw;
}
}
You cannot make the Main() method asynchronous and thus, you can use the await keyword in the body of the Main() function.
A simple workaround that you can implement by editing your current code is making your function TaskTest() return void so you don't have to await it's call.
Example:
public MainPage()
{
InitializeComponent();
TaskTest();
}
private async void TaskTest()
{
try
{
using (WebClient client = new WebClient())
{
label.Text = await client.DownloadStringTaskAsync("https://www.example.com/return.php");
}
}
catch (Exception)
{
throw;
}
}
Edit
In case you have to wait for the return value of an asynchronous call without using await, you could go ahead and use a while to check whether the Task has completed or not.
Task<string> accessTokenTask = Task.Run<string>(() => MethodToGetToken());
// wait until operation is done.
while(!accessTokenTask.IsCompleted)
{
accessTokenTask.Wait():
}
// once the task completes, the runtime will step out of the while loop
// and you can access your Token in the Result
string token = accessTokenTask.Result;
Hope this answers your question.
You probably shouldn't call your Task from MainPage. I started with the Visual Studio blank page and tried to do the same thing. I found an answer suggested to use await Navigation.PushModalAsync(NewPage);, and then call the task there Task.Run(async () => { await method(); }).Wait();. It worked, but not the best way to do it.
This article on CodeProject is great to help beginners to add MVVM to the blank page project. You just need to bind the ViewModel to the MainPage, and then call your Task from the ViewModel instead.
public MainPage()
{
InitializeComponent();
this.BindingContext = new MainPageViewModel(this);
}