Any event raised after an async function does not update UI properly - c#

I am trying to add a basic callback to my app whereby a loading state label updates as the app progresses through an update task. For some reason though, after calling one particular async method, any events raised thereafter are not updating the UI.
I've tried various configure await settings and looked through the async method itself but I can't find anything obviously wrong.
public async Task RefreshTable<T>() where T : class, IEntity
{
try
{
OnContentUpdateProgressChange(this, new MessageEventArgs($"RefreshTable before {typeof(T).Name}"));
var tableJsonData = await ApiService.GetEntityListAsync<T>();
OnContentUpdateProgressChange(this, new MessageEventArgs($"RefreshTable after {typeof(T).Name}"));
await LoadTableData(Set<T>(), tableJsonData);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
public async Task<List<T>> GetEntityListAsync<T>(string url = null) where T : class
{
if (string.IsNullOrEmpty(url))
{
url = typeof(T).Name.Replace("_", "");
}
try
{
string entityList = null;
HttpResponseMessage response = await Client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
entityList = await response.Content.ReadAsStringAsync();
var backupFile = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), typeof(T).Name);
using (var writer = File.CreateText(backupFile))
{
await writer.WriteLineAsync(entityList.ToString());
}
}
return await Task.Factory.StartNew(() => JsonConvert.DeserializeObject<List<T>>(entityList));
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
throw;
}
}
private void DB_OnContentUpdateProgressChange(object sender, library.EventArguments.MessageEventArgs e)
{
lblLoadingState.Text = e.Message;
}
The event raised before the call to GetEntityListAsync successfully updates the UI, the one after however doesn't.

from what I read my understanding tells me this is a UI thread issue When you make some changes in a Background thread in your case the async method it is always recommended that the changes that you have made which will affect the UI should be done on the MainThread or the UIThread. Now what you need to do is quite simple. Whenever you make a change that is supposed to affect the UI in any kind do it on the UI thread.
What I like to do is make all the UI changes in a method then I like to have a base version of my Activity class that has helpers that let me check if it is the MainThread or not and make the changes accordingly use that method.
Add an Activity that inherits from AppCompatActivity and then add the following to it.
public abstract class BaseActivity : AppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
}
public bool IsMainThread => Build.VERSION.SdkInt >= BuildVersionCodes.M
? Looper.MainLooper.IsCurrentThread
: Looper.MyLooper() == Looper.MainLooper;
public void RunOnMainThread(Action action)
{
if (IsMainThread) action();
else RunOnUiThread(action);
}
}
Then make UI changes like:
RunOnMainThread(() => { LabelDoSomething.Text = "New Text"; });

Related

How to call a function in a backgroundworker thread that is to be completed on the main UI thread? [duplicate]

So, first I have read a ton of threads on this particular problem and I still do not understand how to fix it. Basically, I am trying to communicate with a websocket and store the message received in an observable collection that is bound to a listview. I know that I am getting a response back properly from the socket, but when it tries to add it to the observable collection it gives me the following error:
The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
I've read some information on "dispatch" as well as some other things, but I am just massively confused! Here is my code:
public ObservableCollection<string> messageList { get; set; }
private void MessageReceived(MessageWebSocket sender, MessageWebSocketMessageReceivedEventArgs args)
{
string read = "";
try
{
using (DataReader reader = args.GetDataReader())
{
reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
read = reader.ReadString(reader.UnconsumedBufferLength);
}
}
catch (Exception ex) // For debugging
{
WebErrorStatus status = WebSocketError.GetStatus(ex.GetBaseException().HResult);
// Add your specific error-handling code here.
}
if (read != "")
messageList.Add(read); // this is where I get the error
}
And this is the binding:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
//await Authenticate();
Gameboard.DataContext = Game.GameDetails.Singleton;
lstHighScores.ItemsSource = sendInfo.messageList;
}
How do I make the error go away while still binding to the observable collection for my listview?
This solved my issue:
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
// Your UI update code goes here!
}
);
Correct way to get the CoreDispatcher in a Windows Store app
Try replacing
messageList.Add(read);
with
Dispatcher.Invoke((Action)(() => messageList.Add(read)));
If you're calling from outside your Window class, try:
Application.Current.Dispatcher.Invoke((Action)(() => messageList.Add(read)));
Slight modification for task based async methods but the code in here will not be awaited.
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
// Your UI update code goes here!
}
).AsTask();
This code WILL await, and will allow you to return a value:
private async static Task<string> GetPin()
{
var taskCompletionSource = new TaskCompletionSource<string>();
CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
async () =>
{
var pin = await UI.GetPin();
taskCompletionSource.SetResult(pin);
}
);
return await taskCompletionSource.Task;
}
And on Android:
private async Task<string> GetPin()
{
var taskCompletionSource = new TaskCompletionSource<string>();
RunOnUiThread(async () =>
{
var pin = await UI.GetPin();
taskCompletionSource.SetResult(pin);
});
return await taskCompletionSource.Task;
}
Maby this is not a "good" practice, but it works.. I leave a message from webSocket, to mainBody instance, where I have a timered reader...
public class C_AUTHORIZATION
{
public Observer3.A_MainPage_cl parentPageInstance; //еще одни экземпляр родителя
public WebSocket x_Websocket;
private string payload = "";
private DateTime nowMoment = DateTime.Now;
public void GET_AUTHORIZED()
{
bitfinex_Websocket= new WebSocket("wss://*****.com/ws/2");
var apiKey = "";
var apiSecret = "";
DateTime nowMoment = DateTime.Now;
payload = "{}";
x_Websocket.Opened += new EventHandler(websocket_Opened);
x_Websocket.Closed += new EventHandler(websocket_Closed);
}
void websocket_Opened(object sender, EventArgs e)
{
x_Websocket.Send(payload);
parentPageInstance.F_messager(payload);
}
void websocket_Closed(object sender, EventArgs e)
{
parentPageInstance.F_messager("L106 websocket_Closed!");
GET_AUTHORIZED();
}
}
public sealed partial class A_MainPage_cl : Page
{
DispatcherTimer ChartsRedrawerTimer;
public bool HeartBeat = true;
private string Message;
public A_MainPage_cl()
{
this.InitializeComponent();
ChartsRedrawerTimer = new DispatcherTimer() { Interval = new TimeSpan(0, 0, 0, 0, 100) };
ChartsRedrawerTimer.Tick += Messager_Timer;
ChartsRedrawerTimer.Start();
}
private void Messager_Timer(object sender, object e)
{
if(Message !=null) //
{
F_WriteLine(Message);
Message = null; //
}
}
public void F_messager(string message) //
{
Message = message;
}
In Xamarin, I got around this by using:
Device.BeginInvokeOnMainThread(() => {
// code goes here
});

WPF MVVM Async event invoke

I am lost in this one, i want my Viewmodel to use a event delegate so i can subscribe to it, open some dialog and wait for the dialog result. Later the ViewModel should do whatever it wants with the dialog result.
Here is how i implemented it (resumed code):
public class MyViewModel()
{
public delegate TributaryDocument SearchDocumentEventHandler();
public event SearchDocumentEventHandler SearchDocument;
//Command for the search button
public CommandRelay SearchDocumentCommand { get; set; }
//Document that i found in the dialog.
public TributaryDocument Document { get; set; }
public MyViewModel()
{
SearchDocumentCommand = new CommandRelay(DoSearchDocument);
}
//The command execution
public void DoSearchDocument()
{
//Event used here !
Document = SearchDocument?.Invoke();
}
}
public class MyUIControl : UserControl
{
public MainWindow MainWindow { get; }
public MyUIControl()
{
MainWindow = Application.Current.Windows[0] as MainWindow;
DataContextChanged += MyUIControl_DataContextChanged;
}
private void MyUIControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var modelView = (MyViewModel)DataContext;
modelView.SearchDocument += MyUIControl_SearchDocument;
}
private TributaryDocument MyUIControl_SearchDocument()
{
//Dont know what to do here... i am lost on this part.
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
}
//The signature for MainWindow.ShowDialog
public async Task<object> ShowDialog(object dialog)
{
return await DialogHost.Show(dialog, "MainDialog");
}
MyDocumentSearcherDialog is just a dialog where i search and return a TributaryDocument object.
The problem to my understanding comes from this part (since i cant compile it):
private TributaryDocument MyUIControl_SearchDocument()
{
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
I cant use await without changing the method signature to async. If i change it to async then i must return a Task<TributaryDocument> and change the event delegate:
public delegate Task<TributaryDocument> SearchDocumentEventHandler();
//On MyUIControl
private Task<TributaryDocument> MyUIControl_SearchDocument()
{
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
//On MyViewModel
public async void DoSearchDocument()
{
//Event used here !
Document = await Task.Run(async () => await SearchDocument?.Invoke());
}
If i do this i get the following exception:
Additional information: The calling thread must be STA, because many
UI components require this.
It seems like all you need to do is to remove the Task.Run (there is no need to Offload to another thread in this situation). The Task.Run will definitely give you a STA Thread Exception if you are doing UI work from within.
However, in short the Async and Await Pattern will create a continuation with the current SynchronisationContext, so there is no need to worry about it.
public async void DoSearchDocument()
{
await SearchDocument?.Invoke();
}
Note : Since this is an event, it's about the only place it's OK to use async void.

How do I show a modal dialog that tracks a task?

It's clear to me if I have a button that triggers an event, but in the case below, I want to pop up a dialog. The code below is a mess, I don't know how to do this right. I think async/await is part of this, but I'm not clear on this case.
class TaskObject : Form
{
public void MyFunc()
{
MyDialog d = new MyDialog(this);
d.ShowDialog(); // I don't want any other interaction except this dialog's controls
}
internal async Task<bool> LongFunction()
{
// ...
return true;
}
}
class MyDialog : Form
{
Task<bool> task;
public async MyDialog(TaskObject o)
{
task = new Task<bool>(o.LongFunction);
await task;
}
void when_LongFunction_does_something_interesting()
{
this.MyTextBox.Text = "Something interesting";
}
void when_task_completes()
{
this.CancelButton.Visible = false;
this.CloseButton.Visible = true;
}
}
There are two points here:
The constructor of your form cannot have the async modifier. As an alternative, you can use the Load event instead.
(Optional) You don't need to pass an instance of the "parent" form to the constructor, you can get it directly from the Owner property if you use ShowDialog(this) instead of ShowDialog().
Also, remember to dispose of any dialog form after you're done with it. Preferably, wrap the usage of it within a using block.
Here's how I would do it; In the TaskObject form:
internal async Task<bool> LongFunction()
{
// Do some magic.
// await ...
return true;
}
public void MyFunc()
{
using (MyDialog d = new MyDialog())
{
d.ShowDialog(this);
}
}
In the MyDialog form:
private async void MyDialog_Load(object sender, EventArgs e)
{
TaskObject owner = this.Owner as TaskObject;
await owner.LongFunction();
when_task_completes();
}
If you also want to track the progress of LongFunction, you can add a Progress<T> parameter to it and use it like this:
internal async Task<bool> LongFunction(IProgress<string> progress)
{
// Do some magic.
progress.Report("Something interesting");
// await ...
// More magic.
return true;
}
Then you can do something like this:
private async void MyDialog_Load(object sender, EventArgs e)
{
TaskObject owner = this.Owner as TaskObject;
var progress = new Progress<string>(s => when_LongFunction_does_something_interesting(s));
await owner.LongFunction(progress);
when_task_completes();
}
void when_LongFunction_does_something_interesting(string message)
{
this.MyTextBox.Text = message;
}
Note that I used Progress<string> as an example. Instead of string, you can use whatever type works best for your situation.

UWP Update UI From Async Worker

I am trying to implement a long-running background process, that periodically reports on its progress, to update the UI in a UWP app. How can I accomplish this? I have seen several helpful topics, but none have all of the pieces, and I have been unable to put them all together.
For example, consider a user who picks a very large file, and the app is reading in and/or operating on the data in the file. The user clicks a button, which populates a list stored on the page with data from the file the user picks.
PART 1
The page and button's click event handler look something like this:
public sealed partial class MyPage : Page
{
public List<DataRecord> DataRecords { get; set; }
private DateTime LastUpdate;
public MyPage()
{
this.InitializeComponent();
this.DataRecords = new List<DataRecord>();
this.LastUpdate = DateTime.Now;
// Subscribe to the event handler for updates.
MyStorageWrapper.MyEvent += this.UpdateUI;
}
private async void LoadButton_Click(object sender, RoutedEventArgs e)
{
StorageFile pickedFile = // … obtained from FileOpenPicker.
if (pickedFile != null)
{
this.DataRecords = await MyStorageWrapper.GetDataAsync(pickedFile);
}
}
private void UpdateUI(long lineCount)
{
// This time check prevents the UI from updating so frequently
// that it becomes unresponsive as a result.
DateTime now = DateTime.Now;
if ((now - this.LastUpdate).Milliseconds > 3000)
{
// This updates a textblock to display the count, but could also
// update a progress bar or progress ring in here.
this.MessageTextBlock.Text = "Count: " + lineCount;
this.LastUpdate = now;
}
}
}
Inside of the MyStorageWrapper class:
public static class MyStorageWrapper
{
public delegate void MyEventHandler(long lineCount);
public static event MyEventHandler MyEvent;
private static void RaiseMyEvent(long lineCount)
{
// Ensure that something is listening to the event.
if (MyStorageWrapper.MyEvent!= null)
{
// Call the listening event handlers.
MyStorageWrapper.MyEvent(lineCount);
}
}
public static async Task<List<DataRecord>> GetDataAsync(StorageFile file)
{
List<DataRecord> recordsList = new List<DataRecord>();
using (Stream stream = await file.OpenStreamForReadAsync())
{
using (StreamReader reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
// Does its parsing here, and constructs a single DataRecord …
recordsList.Add(dataRecord);
// Raises an event.
MyStorageWrapper.RaiseMyEvent(recordsList.Count);
}
}
}
return recordsList;
}
}
The code for the time check I got from following this.
As written, this code makes the app unresponsive with a large file (I tested on a text file on the order of about 8.5 million lines). I thought adding async and await to the GetDataAsync() call would prevent this? Does this not do its work on a thread aside from the UI thread? Through Debug mode in Visual Studio, I have verified the program is progressing as expected... it is just tying up the UI thread, making the app unresponsive (see this page from Microsoft about the UI thread and asynchronous programming).
PART 2
I have successfully implemented before an asynchronous, long-running process that runs on a separate thread AND still updates the UI periodically... but this solution does not allow for the return value - specifically the line from PART 1 that says:
this.DataRecords = await MyStorageWrapper.GetDataAsync(pickedFile);
My previous, successful implementation follows (most of the bodies cut out for brevity). Is there a way to adapt this to allow for return values?
In a Page class:
public sealed partial class MyPage : Page
{
public Generator MyGenerator { get; set; }
public MyPage()
{
this.InitializeComponent();
this.MyGenerator = new Generator();
}
private void StartButton_Click(object sender, RoutedEventArgs e)
{
this.MyGenerator.ProgressUpdate += async (s, f) => await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, delegate ()
{
// Updates UI elements on the page from here.
}
this.MyGenerator.Start();
}
private void StopButton_Click(object sender, RoutedEventArgs e)
{
this.MyGenerator.Stop();
}
}
And in the Generator class:
public class Generator
{
private CancellationTokenSource cancellationTokenSource;
public event EventHandler<GeneratorStatus> ProgressUpdate;
public Generator()
{
this.cancellationTokenSource = new CancellationTokenSource();
}
public void Start()
{
Task task = Task.Run(() =>
{
while(true)
{
// Throw an Operation Cancelled exception if the task is cancelled.
this.cancellationTokenSource.Token.ThrowIfCancellationRequested();
// Does stuff here.
// Finally raise the event (assume that 'args' is the correct args and datatypes).
this.ProgressUpdate.Raise(this, new GeneratorStatus(args));
}
}, this.cancellationTokenSource.Token);
}
public void Stop()
{
this.cancellationTokenSource.Cancel();
}
}
Finally, there are two supporting classes for the ProgressUpdate event:
public class GeneratorStatus : EventArgs
{
// This class can contain a handful of properties; only one shown.
public int number { get; private set; }
public GeneratorStatus(int n)
{
this.number = n;
}
}
static class EventExtensions
{
public static void Raise(this EventHandler<GeneratorStatus> theEvent, object sender, GeneratorStatus args)
{
theEvent?.Invoke(sender, args);
}
}
It is key to understand that async/await does not directly say the awaited code will run on a different thread. When you do await GetDataAsync(pickedFile); the execution enters the GetDataAsync method still on the UI thread and continues there until await file.OpenStreamForReadAsync() is reached - and this is the only operation that will actually run asynchronously on a different thread (as file.OpenStreamForReadAsync is actually implemented this way).
However, once OpenStreamForReadAsync is completed (which will be really quick), await makes sure the execution returns to the same thread it started on - which means UI thread. So the actual expensive part of your code (reading the file in while) runs on UI thread.
You could marginally improve this by using reader.ReadLineAsync, but still, you will be returning to UI thread after each await.
ConfigureAwait(false)
The first trick you want to introduce to resolve this problem is ConfigureAwait(false).
Calling this on an asynchronous call tells the runtime that the execution does not have to return to the thread that originally called the asynchronous method - hence this can avoid returning execution to the UI thread. Great place to put it in your case is OpenStreamForReadAsync and ReadLineAsync calls:
public static async Task<List<DataRecord>> GetDataAsync(StorageFile file)
{
List<DataRecord> recordsList = new List<DataRecord>();
using (Stream stream = await file.OpenStreamForReadAsync().ConfigureAwait(false))
{
using (StreamReader reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
string line = await reader.ReadLineAsync().ConfigureAwait(false);
// Does its parsing here, and constructs a single DataRecord …
recordsList.Add(dataRecord);
// Raises an event.
MyStorageWrapper.RaiseMyEvent(recordsList.Count);
}
}
}
return recordsList;
}
Dispatcher
Now you freed up your UI thread, but introduced yet another problem with the progress reporting. Because now MyStorageWrapper.RaiseMyEvent(recordsList.Count) runs on a different thread, you cannot update the UI in the UpdateUI method directly, as accessing UI elements from non-UI thread throws synchronization exception. Instead, you must use UI thread Dispatcher to make sure the code runs on the right thread.
In the constructor get reference to the UI thread Dispatcher:
private CoreDispatcher _dispatcher;
public MyPage()
{
this.InitializeComponent();
_dispatcher = Window.Current.Dispatcher;
...
}
Reason to do it ahead is that Window.Current is again accessible only from the UI thread, but the page constructor definitely runs there, so it is the ideal place to use.
Now rewrite UpdateUI as follows
private async void UpdateUI(long lineCount)
{
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
// This time check prevents the UI from updating so frequently
// that it becomes unresponsive as a result.
DateTime now = DateTime.Now;
if ((now - this.LastUpdate).Milliseconds > 3000)
{
// This updates a textblock to display the count, but could also
// update a progress bar or progress ring in here.
this.MessageTextBlock.Text = "Count: " + lineCount;
this.LastUpdate = now;
}
});
}

How to solve this deadlock?

I have an UWP application in which I am trying to store and retrieve some data from a local text file but no matter how I try to do it the application gets deadlocked. Due to synchronous stuff that needs to happen, I try to use a task and wait for its completion but nevertheless, the application locks.
I have a page in my UWP application called "MainPage" and in it's constructor I have the following code:
var listenkeyViewModel = new ListenkeyViewModel();
listenkeyViewModel.GetKey();
listenkey = listenkeyViewModel.Listenkey;
The get key is the issue here because it calls a method on the ViewModel (which I created to be synchronous because I thought making the call synchronous asap would be preferable.
public void GetKey()
{
try
{
var listenKeyTask = RetrieveListenKey();
_listenkey = listenKeyTask.Result;
}
catch (Exception e)
{
}
}
public static async Task<string> RetrieveListenKey()
{
try
{
var storageFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
var listenkeyFile = await storageFolder.GetFileAsync("listenkey.txt");
return await Windows.Storage.FileIO.ReadTextAsync(listenkeyFile);
}
catch (Exception e)
{
throw new Exception("Could not load file");
}
}
I know the thing is "async all the way down" but this is not possible here. I cannot make the constructor where the original code lies asynchronous. How are you supposed to not get deadlocked? I do not understand.
Convert GetKey to async/await
public async Task GetKey() {
try {
var listenKeyTask = RetrieveListenKey();
_listenkey = await listenKeyTask;
} catch (Exception e) {
//...should handle/log error
}
}
Move the calling of this out of the constructor and into an event handler. like page load or some other event called early in the lifecycle of the page.
partial class MainPage : Page {
ListenkeyViewModel listenkeyViewModel;
string listenkey;
public MainPage() {
InitializeComponent();
listenkeyViewModel = new ListenkeyViewModel();
// add a handler to be called when the page has been loaded
this.Loaded += OnPageLoaded;
}
async void OnPageLoaded(object sender, RoutedEventArgs e) {
await listenkeyViewModel.GetKey();
listenkey = listenkeyViewModel.Listenkey;
}
// Shown for demonstration purposes only.
// This is typically autogenerated by Visual Studio.
private void InitializeComponent() {
}
}
async void is allowed on event handlers so this should allow the process to flow without deadlock.

Categories

Resources