WPF UI not updating and not sure why? - c#

I had a timer in my app that was causing too many thread locks so I decided to replace the timer with a task that runs in a loop. Now, when using the task my UI properties are not updating correctly. I figured it was a thread issue so I move the UI updates outside of the task and used await, but the UI still isn't working. In debug I following the code to the UI and it's on the main thread with the correct data but the UI never updates. The symptom is a frozen UI and a process memory that just keep growing. I"m lost on how to fix this one. Maybe someone can help?
private static void OnStaticPropertyChanged([CallerMemberName] string propertyName = null)
{ StaticPropertyChanged?.Invoke(null, new propertyChangedEventArgs(propertyName));
}
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Dispatcher.BeginInvoke((Action)(() =>
{
switch (e.PropertyName)
{
case "SiderealTime":
TextLst.Content = _util.HoursToHMS(TelescopeHardware.SiderealTime);
break;
case "RightAscension":
TextRa.Content = _util.HoursToHMS(TelescopeHardware.RightAscension, "h ", "m ", "s", 3);
break;
}));
}
private static async void Main_Mount_Loop()
{
_cts = new CancellationTokenSource();
var ct = _cts.Token;
var keepGoing = true;
while (keepGoing)
{
var task = Task.Run(() =>
{
ct.ThrowIfCancellationRequested();
Task.Delay(1000, ct);
MoveAxes();
if (ct.IsCancellationRequested)
{
// Clean up here, then...
keepGoing = false;
}
}, ct);
await task;
UpdateUi();
}
}

Reactive extensions seem to be a more suited library for this usage:
Observable
.Interval(TimeSpan.FromSeconds(1), ThreadPoolScheduler.Instance)
.Do(_ => MoveAxes())
.SubscribeOn(DispatcherScheduler.Current)
.Subscribe(_ => UpdateUi());

Related

Updating status strip label async/await

I have a WinForm, with a toolStripStatusLabel. There is a button, which spawns a new thread to perform its task. The status label needs to update during, and after this task is completed. The GUI elements are in the main thread. If I want to achieve this, can I place the relevant lines to update the label where the comments are below in the code snippet below? Also, I need to have another form open when this label is clicked. From my understanding of asynchronous coding, this should be straightforward, involving an event handler for the label, and the fact that control will return to the caller of the async method. Is this correct? I am relatively new to multithreaded and asynchronous programming, so I am quite confused.
// this is running in the main GUI thread
private async void Export_execute_Click(object sender, System.EventArgs args)
{
try
{
await System.Threading.Tasks.Task.Run(() => do_export(filename, classes, System.TimeZoneInfo.ConvertTimeToUtc(timestamp)));
// if this is successful, status label should be update (task successful)
}
catch (System.Exception e)
{
// status label should be updated (task failed)
}
}
If there is something literally awaitable in the Export method then I think to make it an async method would be better.
private async void Export_execute_Click(object sender, EventArgs e)
{
try
{
await ExportAsync("file1", "classA", DateTime.Now);
toolStripStatusLabel.Text = $"Export finished at {DateTime.Now}";
}
catch (Exception ex)
{
toolStripStatusLabel.Text = $"Export failed, {ex.ToString()}";
}
}
private async Task ExportAsync(string fileName, string classes, DateTime timestamp)
{
toolStripStatusLabel.Text = $"Export start at {timestamp}";
await Task.Delay(TimeSpan.FromSeconds(5));
toolStripStatusLabel.Text = $"Have first half done {timestamp}";
await Task.Delay(TimeSpan.FromSeconds(5));
}
private void toolStripStatusLabel_Click(object sender, EventArgs e)
{
Form2 frm2 = new Form2();
frm2.Show();
}
The standard way to report progress is to use the IProgress<T> interface. There is already an implementation of this interface that you can use (Progress<T>), and is generic so that you can supply any type of argument you want. In the example bellow the argument is a string. The key point is that the event Progress.ProgressChanged is running in the UI thread, so you don't have to worry about it.
// This will run in the UI thread
private async void Export_Execute_Click(object sender, EventArgs args)
{
try
{
var progress = new Progress<string>();
progress.ProgressChanged += ExportProgress_ProgressChanged;
// Task.Factory.StartNew allows to set advanced options
await Task.Factory.StartNew(() => Do_Export(filename, classes,
TimeZoneInfo.ConvertTimeToUtc(timestamp), progress),
CancellationToken.None, TaskCreationOptions.LongRunning,
TaskScheduler.Default);
toolStripStatusLabel.Text = $"Export completed successfully";
}
catch (Exception e)
{
toolStripStatusLabel.Text = $"Export failed: {e.Message}";
}
}
// This will run in the UI thread
private void ExportProgress_ProgressChanged(object sender, string e)
{
toolStripStatusLabel.Text = e;
}
// This will run in a dedicated background thread
private void Do_Export(string filename, string classes, DateTime timestamp,
IProgress<string> progress)
{
for (int i = 0; i < 100; i += 10)
{
progress?.Report($"Export {i}% percent done");
Thread.Sleep(1000);
}
}
How about a BackgroundWorker instead of your current Task? I prefer these because they allow easy communication between the main thread and the worker.
Note that Export_execute_Click is no longer marked as async in this scenario.
Example:
private void Export_execute_Click(object sender, System.EventArgs args) {
// Method level objects are accessible throughout this process
bool error = false;
// Process
BackgroundWorker worker = new BackgroundWorker {
WorkerReportsProgress = true
};
// This executes on main thread when a progress is reported
worker.ProgressChanged += (e, ea) => {
if (ea.UserState != null) {
// ea.UserState.ToString() contains the string progress message
}
};
// This executes as an async method on a background thread
worker.DoWork += (o, ea) => {
try {
var response = do_export(filename, classes, System.TimeZoneInfo.ConvertTimeToUtc(timestamp)));
if (response == whatever) {
worker.ReportProgress(0, "Response from do_export() was `whatever`");
} else {
worker.ReportProgress(0, "Response from do_export() was something bad");
error = true;
}
} catch (System.Exception e) {
worker.ReportProgress(0, $"do_export() failed: {e}");
}
};
// This executes on the main thread once the background worker has finished
worker.RunWorkerCompleted += async (o, ea) => {
// You can communicate with your UI normally again here
if (error) {
// You had an error -- the exception in DoWork() fired
} else {
// You're all set
}
// If you have a busy-indicator, here is the place to disable it
// ...
};
// I like to set a busy-indicator here, some sort of ajax-spinner type overlay in the main UI, indicating that the process is happening
// ...
// This executes the background worker, as outlined above
worker.RunWorkerAsync();
}

How to wait for a response in wpf

So I am talking to something externally and I have just sent it a message, I will expect an almost immediate response but I will wait for a second in case there is a delay. A separate thread is monitoring for an input and will set a flag "newdataflag" when it has received this data. All I am trying to do below is wait in a while loop until this flag is set or 1 second elapses.
private bool WaitrxData()
{
System.Windows.Threading.DispatcherTimer waitrxtimer = new System.Windows.Threading.DispatcherTimer();
waitrxtimer.Tick += waitrxtimer_Tick;
waitrxtimer.Interval = TimeSpan.FromMilliseconds(10);
waitrxtimer.IsEnabled = true;
waitrxtimer.Start();
statusText.Text = "Waiting For Response";
//wait for new data
while (!newdataflag)
{
if (waitrxcounter > 100)
{
statusText.Text = "No Response";
break;
}
}
waitrxtimer.Stop();
if (waitrxcounter > 100)
{
return false;
}
else
{
newdataflag = false;
return true;
}
}
private void waitrxtimer_Tick(object sender, EventArgs e)
{
waitrxcounter++;
}
This code works if the response is so immediate the while loop is skipped but if not, the code will not execute the timer and just get stuck in the while loop and crash. I think this is because the timer is not creating a new thread to tick like I thought it would?
Maybe I am doing the wrong thing?
Cheers
Instead of busy waiting in a loop you could await Task.Delay() with a CancellationToken:
private CancellationTokenSource cts = new CancellationTokenSource();
...
private async Task<bool> Wait()
{
try
{
await Task.Delay(1000, cts.Token);
return true;
}
catch
{
return false;
}
}
// cancel Wait() from another method
cts.Cancel();

Why is My Async Timer Blocking the UI Thread?

I have a timer setup in my mainwindow code behind that fires every ten seconds. Because some of the code referenced in the timer_Elapsed event is somewhat CPU intensive I have placed it inside an await Task.Run(() =>, however the UI thread continues to hang momentarily whenever the elapsed event runs. Any ideas why this would be blocking the UI? Code:
async void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
await Task.Run(() =>
{
//Update default status bar text routinely
try
{
if (ChecEnabled())
{
this.Dispatcher.Invoke(() =>
{
StatusText.Text = String.Format("Status: Enabled. Watching for changes…");
});
}
else
{
this.Dispatcher.Invoke(() =>
{
StatusText.Text = String.Format("Status: Disabled");
});
}
}
catch (ObjectDisposedException)
{
//Window closed and disposed timer on different thread
}
//System Checks
UpdateSystemReadyStatus();
});
}
Update your Invoke to InvokeAsync. Also, do you really need to entire method wrapped in a Task?
async void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
//Update default status bar text routinely
try
{
if (ChecEnabled())
{
await this.Dispatcher.InvokeAsync(() =>
{
StatusText.Text = String.Format("Status: Enabled. Watching for changes…");
});
}
else
{
await this.Dispatcher.InvokeAsync(() =>
{
StatusText.Text = String.Format("Status: Disabled");
});
}
}
catch (ObjectDisposedException)
{
//Window closed and disposed timer on different thread
}
//System Checks
await Task.Run(()=>UpdateSystemReadyStatus());
}

Async/await for long-running API methods with progress/cancelation

Edit
I suppose the proper way of forcing await to invoke the worker asynchronously is with a Task.Run, like this:
await Task.Run(() => builder.Build(dlg.FileName, cts.Token, new Progress(ReportProgress)));
Got some light from http://blogs.msdn.com/b/pfxteam/archive/2012/04/12/10293335.aspx.
this should be easy but I'm new to async/await so bear with me. I am building a class library exposing an API with some long-running operations. In the past, I used a BackgroundWorker to deal with progress reporting and cancelation, like in this simplified code fragment:
public void DoSomething(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = (BackgroundWorker)sender;
// e.Argument is any object as passed by consumer via RunWorkerAsync...
do
{
// ... do something ...
// abort if requested
if (bw.CancellationPending)
{
e.Cancel = true;
break;
} //eif
// notify progress
bw.ReportProgress(nPercent);
}
}
and the client code was like:
BackgroundWorker worker = new BackgroundWorker
{ WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
worker.DoWork += new DoWorkEventHandler(_myWorkerClass.DoSomething);
worker.ProgressChanged += WorkerProgressChanged;
worker.RunWorkerCompleted += WorkerCompleted;
worker.RunWorkerAsync(someparam);
Now I'd like to leverage the new async pattern. So, first of all here is how I'd write a simple long-running method in my API; here I'm just reading a file line by line, just to emulate a real-world process where I'll have to convert a file format with some processing:
public async Task DoSomething(string sInputFileName, CancellationToken? cancel, IProgress progress)
{
using (StreamReader reader = new StreamReader(sInputFileName))
{
int nLine = 0;
int nTotalLines = CountLines(sInputFileName);
while ((sLine = reader.ReadLine()) != null)
{
nLine++;
// do something here...
if ((cancel.HasValue) && (cancel.Value.IsCancellationRequested)) break;
if (progress != null) progress.Report(nLine * 100 / nTotalLines);
}
return nLine;
}
}
For the sake of this sample, say this is a method of a DummyWorker class. Now, here is my client code (a WPF test app):
private void ReportProgress(int n)
{
Dispatcher.BeginInvoke((Action)(() => { _progress.Value = n; }));
}
private async void OnDoSomethingClick(object sender, RoutedEventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog { Filter = "Text Files (*.txt)|*.txt" };
if (dlg.ShowDialog() == false) return;
// show the job progress UI...
CancellationTokenSource cts = new CancellationTokenSource();
DummyWorker worker = new DummyWorker();
await builder.Build(dlg.FileName, cts.Token, new Progress(ReportProgress));
// hide the progress UI...
}
The implementation for the IProgress interface comes from http://blog.stephencleary.com/2010/06/reporting-progress-from-tasks.html, so you can refer to that URL. Anyway, in this usage test the UI is effectively blocked and I see no progress. So what would be the full picture for such a scenario, with reference to the consuming code?
As noted on the top of that blog post, the information in that post is outdated. You should use the new IProgress<T> API provided in .NET 4.5.
If you're using blocking I/O, then make your core method blocking:
public void Build(string sInputFileName, CancellationToken cancel, IProgress<int> progress)
{
using (StreamReader reader = new StreamReader(sInputFileName))
{
int nLine = 0;
int nTotalLines = CountLines(sInputFileName);
while ((sLine = reader.ReadLine()) != null)
{
nLine++;
// do something here...
cancel.ThrowIfCancellationRequested();
if (progress != null) progress.Report(nLine * 100 / nTotalLines);
}
return nLine;
}
}
and then wrap it in Task.Run when you call it:
private async void OnDoSomethingClick(object sender, RoutedEventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog { Filter = "Text Files (*.txt)|*.txt" };
if (dlg.ShowDialog() == false) return;
// show the job progress UI...
CancellationTokenSource cts = new CancellationTokenSource();
DummyWorker worker = new DummyWorker();
var progress = new Progress<int>((_, value) => { _progress.Value = value; });
await Task.Run(() => builder.Build(dlg.FileName, cts.Token, progress);
// hide the progress UI...
}
Alternatively, you could rewrite Build to use asynchronous APIs and then just call it directly from the event handler without wrapping it in Task.Run.

Task continuation to maintain UI thread responsiveness

I'm using Tasks in WinForms to remove expensive methods from my UI thread. In my updateComplete and updateFailed tasks, I have to set _updateMessageTaskInProgress to false and enable my controls. Is there any way I can do this in a separate task which either updateComplete or updateFailed continues too once either is complete (as I currently have duplicate code)? Plus, is there a better way of implementing _updateMessageTaskInProgress - I don't want more than one task to run at the same time.
private void PerformUpdate()
{
if (!_updateMessageTaskInProgress)
{
LoadButton.Enabled = false;
MonthEndDateEdit.Enabled = false;
BankIssuerListEdit.Enabled = false;
Task updateMessages = Task.Factory.StartNew(() =>
{
_updateMessageTaskInProgress = true;
ExpensiveMethod();
});
// Task runs when updateMessages completes without exception. Runs on UI thread.
Task updateComplete = updateMessages.ContinueWith(update =>
{
DoSuccessfulStuff();
_updateMessageTaskInProgress = false;
LoadButton.Enabled = true;
MonthEndDateEdit.Enabled = true;
BankIssuerListEdit.Enabled = true;
}, System.Threading.CancellationToken.None, TaskContinuationOptions.NotOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
// Task runs when updateMessages completes with exception. Runs on UI thread.
Task updateFailed = updateMessages.ContinueWith(task =>
{
DoFailureStuff();
_updateMessageTaskInProgress = false;
LoadButton.Enabled = true;
MonthEndDateEdit.Enabled = true;
BankIssuerListEdit.Enabled = true;
}, System.Threading.CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
}
Why don't you just extract a method?
private void SetLock(bool lock)
{
LoadButton.Enabled = !lock;
MonthEndDateEdit.Enabled = !lock;
BankIssuerListEdit.Enabled = !lock;
_updateMessageTaskInProgress = lock;
}
private void PerformUpdate()
{
if (!_updateMessageTaskInProgress)
{
SetLock(true);
Task updateMessages = Task.Factory.StartNew(() =>
{
ExpensiveMethod();
});
// Task runs when updateMessages completes without exception. Runs on UI thread.
Task updateComplete = updateMessages.ContinueWith(update =>
{
DoSuccessfulStuff();
SetLock(false);
}, System.Threading.CancellationToken.None, TaskContinuationOptions.NotOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
// Task runs when updateMessages completes with exception. Runs on UI thread.
Task updateFailed = updateMessages.ContinueWith(task =>
{
DoFailureStuff();
SetLock(false);
}, System.Threading.CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
}
}
I would use the Event Based Asynchronous-TYPE Pattern for this. A simplified version of the code I use to spin-off method onto a background thread using TPL is below
private void TaskSpin(TaskScheduler uiScheduler,
Func<TaskScheduler, object[], bool> asyncMethod,
object[] methodParameters)
{
try
{
Task asyncTask = Task.Factory.StartNew<bool>(() =>
asyncMethod(uiScheduler, methodParameters));
// Callback for finish/cancellation.
asyncTask.ContinueWith(task =>
{
// Check task status.
switch (task.Status)
{
// Handle any exceptions to prevent UnobservedTaskException.
case TaskStatus.RanToCompletion:
if (asyncTask.Result)
UpdateUI(uiScheduler, "OK");
else
{
string strErrComplete = "Process failed.";
UpdateUI(uiScheduler, strErrComplete);
}
break;
case TaskStatus.Faulted:
string strFatalErr = String.Empty;
UpdateUI(uiScheduler, "Fatal Error);
if (task.Exception != null)
strFatalErr = task.Exception.InnerException.Message;
else
strFatalErr = "Operation failed";
MessageBox.Show(strFatalErr);
break;
}
asyncTask.Dispose();
return;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception eX)
{
Utils.ErrMsg(eX.Message);
}
}
I hope this helps.
Edit. Note, in the above uiScheduler is the TaskScheduler for the UI Thread. That is
TaskSheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

Categories