Observe property change multiple times in one WPF event using MVVM? - c#

I was wanting to do a costly operation and post back to a user 'where' in the state of operation a method was. Basically I am using MVVM to bind an ICommand to a button click event. That event triggers a dialogue for a user, the file they select then is a word document that is parsed, then a form is filled with that word document. The problem I run into with standard operation is that Text displays only the LAST change to the property. I have set breakpoints and I see that the property gets raised, however it seems that ICommand argument waits till ALL WORK is finished and then updates only the last property. Is there a way around this to show posts backs to a user, while the process is happening?
**So essentially what I want is a user to click a button and see "Obtained Word Document", (work then is done) "Parsed Word Document" one after the other as the process completes. NOT the last change when the ICommand finishes. I think the core issue is that the UI is not getting the changes till the stack pauses that is inside either a 'Relay Command'/'Async Relay Command' delegate method. **
XAML:
<TextBox Text="{Binding WordFileLocation}" />
<Button Content="Start Process" Height="20" Command="{Binding AsyncDoCommand}"/>
<TextBox Text="{Binding Text, IsAsync=True}" />
VIEWMODEL:
private Reader _wordReader = new Reader();
private string _ParsedWordString;
private AsyncRelayCommand _DoAsyncCommand;
private string _Text;
private string _WordFileLocation;
public string Text
{
get { return _Text; }
set
{
_Text = value;
RaisePropertyChanged("Text");
}
}
public string WordFileLocation
{
get { return _WordFileLocation; }
set
{
_WordFileLocation = value;
RaisePropertyChanged("WordFileLocation");
}
}
public ICommand AsyncDoCommand
{
get
{
if (_DoAsyncCommand == null)
{
_DoAsyncCommand = new AsyncRelayCommand(async () => await DoIt());
}
return _DoAsyncCommand;
}
}
public async Task DoIt()
{
WordFileLocation = "Somewhere a dialogue selected...";
Text = "Looking....";
await Task.Delay(2000);
Text = "Look at me"; // Works FINALLY....
await GetWordData();
// If I put in the delay below, the Text change will show up. If not it won't. For some reason my setting of Text DOES not show up till a delay is triggered.
//await Task.Delay(100);
await ParseWordData();
}
async Task ParseWordData()
{
try
{
_ParsedWordString = _wordReader.ReadWordDocWithForms(_WordFileLocation);
Text = "Parsed Word Document";
}
catch (Exception)
{
Text = "Could not parse Word Document";
}
}
async Task GetWordData()
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.Multiselect = false;
dlg.Filter = "Doc Files (*.doc, *.docx)|*.doc;*.docx";
// open dialog
bool ok = (bool)dlg.ShowDialog();
if(ok)
{
try
{
// Get the location from the dialog
WordFileLocation = dlg.FileName;
Text = "Obtained Word Document.";
}
catch (Exception)
{
Text = "Failed Loading Document.";
}
}
else
{
Text = "Could Not Browse for Document.";
}
}
EDIT 8-20-14 12:45 PST:
Tseng is correct except for one thing. I cannot get the UI to accept the async changes UNLESS I force a 'Task.Delay(100)'. It is like the stack wants to auto finish through my two sub methods. I am a total noob at the .NET 4.5 async methods, but I want to use them as they seem to be the preferred way. I am guessing it is my ignorance in understanding the 'Task' and what it does. I have to do a Task return but it seems await does not like to do something as simple as 'await "Loaded"' or similar. So I have tried return types in my signature method like 'void', Task, Task with a simple 'return "Obtained Document"'. None of this updates the Property, UNTILL I call Task.Delay() AFTER the sub method. So it is my ignorance of understanding the async process of why I need to pause to just get an update. The 'ParseWordDocument' is pretty expensive as it is parsing long word documents and on average takes 2 to 5 seconds depending on the doc size as it is parsing out form fills as well as plain text. However even with this delay my text is not getting updated till this sub method is done.

I'd suggest you to use an async command implementation, like AsyncRelayCommand found on the internet.
I use this implementation for one of my own MVVM Projects.
public class AsyncRelayCommand : ICommand {
protected readonly Func<Task> _asyncExecute;
protected readonly Func<bool> _canExecute;
public event EventHandler CanExecuteChanged {
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public AsyncRelayCommand(Func<Task> execute)
: this(execute, null) {
}
public AsyncRelayCommand(Func<Task> asyncExecute, Func<bool> canExecute) {
_asyncExecute = asyncExecute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) {
if(_canExecute == null) {
return true;
}
return _canExecute();
}
public async void Execute(object parameter) {
await ExecuteAsync(parameter);
// notify the UI that the commands can execute changed may have changed
RaiseCanExecuteChanged();
}
protected virtual async Task ExecuteAsync(object parameter) {
await _asyncExecute();
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
This has the additional benefit, that you can't only run the command async and do UI operations inbetween (i.e. add to ObservableCollection) but you can also notify the UI when the CanExecute status may be changed (i.e. when the command is finished).
Example usage:
public ICommand DoCommand
{
get
{
if(_DoCommand == null)
{
_DoCommand = new AsyncRelayCommand(DoIt);
}
return _DoCommand;
}
}
public async void DoIt() {
WordFileLocation = "Someplace a dialogue selected";
await ParseDocument();
Text = "Parsed Word Document";
await ObtainDocument();
Text = "Obtained Word Document.";
}
Edit:
WPF Command bindings are async/Task aware. If your ICommand.Execute returns Task or Task<T>, then WPF will run them asynchronously.
You really need to make sure that both, criteria are met:
Your DoIt() Method has the async keyword (C# 5.0/.NET 4.5) (or returns Task rather than being void, for .NET 3.5 and 4.0)
You use await for EVERY long processing. If your method returns an awaitable/Task/Task<T> you can await on it. If your methods doesn't, you can still create a new Task and await it
Another example of the DoIt() Method
public Task ParseDocumentAsync()
{
return Task.Run( () => {
// your long processing parsing code here
});
}
public async void DoIt() {
WordFileLocation = "Someplace a dialogue selected";
Text = "Begin";
await ParseDocumentAsync(); // public Task ParseDocumentAsync() { }
Text = "ParseDocumentDone()";
Text = "Wait 3 seconds";
await Task.Delay(3000);
Text = "Run non-Task methods";
Task.Run( () => LongRunningNonAsyncMethod(); );
Text = "LongRunningNonAsyncMethod() finished. Wait 2 seconds";
// DON'T DO THIS. It will block the UI thread!
// It has no await, it runs on the thread which started everything,
// which is UI Thread in this case, because the View invoked the command.
// That's why it locks the UI
Thread.Sleep(2000);
Text = "Waited 2 seconds. We won't see this, because UI is locked";
// DON'T DO THIS, it will ALSO block the UI Thread.
LongRunningNonAsyncMethod();
Text = "Finished";
}
On a side note: If you are using .NET 4.5 and C# 5.0, you can use async/await keywords for async operations. If you are forced to use older Frameworks (.NET 3.5 and 4.0), you can still use Task t = Task.Run(...) to start it and `t.ContinueWith( () => { Text = "Finished" } )´ to execute code after the task is finished.
Edit2:
Sorry for the late reply, I was busy with RL work, didn't had much time to watch in here. I'll update your ParseWordData() method and hope it works then.
// alternatively: async void ParseWordData().
// async void => Task as return type
// async Task => Task<Task> as return type
Task ParseWordData()
{
return Task.Run( () => {
try
{
_ParsedWordString = _wordReader.ReadWordDocWithForms(_WordFileLocation);
Text = "Parsed Word Document";
}
catch (Exception)
{
Text = "Could not parse Word Document";
}
});
}
This will run the ReadWordDocWithForms code inside a thread/Task and return the Task. The Task can be awaited.
Basically it boils down to: use await on awaitable methods (which return Task or Task<T>) and if you need to run a method which isn't awaitable, use Task.Run(...) and return (or await) this Task.

I'm unable to add just a comment so I'll go with an answer.
The ICommand will use the base UI thread to do its processing, so you will not be able to accomplish this without setting up a task of some sort.
It sounds like you know how to do that but in case, this is how I would do it:
Text = "Parsed Word Document";
Task.Factory.StartNew(() =>
{
//do your "DoIt" work here
Text = "Obtained Word Document.";
});
Edit:
public ICommand DoCommand
{
get
{
if (_DoCommand == null)
{
_DoCommand = new RelayCommand(Param => NewMethod(new Action(()=>DoIt())));
}
return _DoCommand;
}
}
NewMethod(Action DoIt)
{
Task.Factory.StartNew(() =>
{
DoIt.Invoke();
});
}
The Linq statement in the RelayComand Is a bit messy, but this does allow you to reuse "NewMethod" in any place you need to pop off a task.
Otherwise, you could simply call DoIt() from the newmethod and save yourself the Action parameter.

Related

Awaited method call doesnt appear to complete [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I have just started to learn asynchronous coding and have run into a problem.
So this is what I am trying to do.
I have a method which calls two awaited methods asynchronously. For testing purposes each method is calling a stored procedure which executes WAITFOR DELAY 'xxx'.
Within each method call another method needs to run alongside it to generate three loading dots appended to the text value passed in.
So when await Task.Run(() => DisableReplication("Disable Replication")) is running (calling the stored procedure for say 10 secs) it will display
Disabling Replication.
Disabling Replication..
Disabling Replication...
until that stored procedure has finished, then it should say
Disabling Replication - Complete
Then it should call await Task.Run(() => ImportWeights("Importing Weights")), where for example the proc takes 20 secs to complete. So it will display
Importing Weights.
Importing Weights..
Importing Weights...
Then once complete it will say
Import Weights - Complete.
Within each method there is a bool value being set from false to true once the method has finished. That value is used in the ShowProgressText method in the while loop. Once the value is set to true it should break out.
At present it is doing the 1st part fine with displaying 'Disabling Replication' but once that method is completed it starts switching between 'Disabling Replication' and 'Importing Weights'. I assumed the Disabling Replication method was finished so I am not sure why it continues to pick the text up. I think the issue is with the ShowProgressText method.
Does anyone known what I am doing wrong and perhaps if I could simplify this somehow?
Here is a .gif showing what it is doing and the code I am using is below.
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DirectMailSpendManagement
{
public partial class FrmRapport : AestheticsFormBase
{
#region Instantiations
private readonly RapportDataAccess access = new RapportDataAccess();
private readonly Panel panel = new Panel();
#endregion
#region Fields
private bool IsCompleted;
#endregion
#region Constructors
public FrmRapport()
{
InitializeComponent();
}
#endregion
#region Private Methods
private void BtnGetRapportFile(object sender, EventArgs e)
{
_ = LoadRapport();
}
private async Task LoadRapport()
{
await Task.Run(() => DisableReplication("Disable Replication"));
await Task.Run(() => ImportWeights("Importing Weights"));
}
private void DisableReplication(string txt)
{
IsCompleted = false;
Task.Run(() => ShowProgressText(txt));
access.DisableReplication();
}
private void ImportWeights(string txt)
{
IsCompleted = false;
Task.Run(() => ShowProgressText(txt));
access.ImportAndCalculateWeights(panel.PanelNumber, "", "", "");
}
private void ShowProgressText(string txt)
{
var count = 0;
var logText = new StringBuilder();
logText.Append(txt);
var baseLen = logText.Length;
while (!IsCompleted)
{
Thread.Sleep(300);
if (count >= 3)
{
logText.Remove(baseLen, count);
count = 0;
}
logText.Append(".");
count++;
BeginInvoke(new Action(() => { UpdateProgressText(logText.ToString()); }));
}
BeginInvoke(new Action(() => { UpdateProgressText(txt + " - Complete"); }));
}
private void UpdateProgressText(string txt)
{
lblProgress.Text = txt;
}
#endregion
}
}
The problem is you have two separate tasks running that call ShowProgressText. Task.Run() is not something you normally use unless you are interfacing with code that does not use the C# async/await pattern. So perhaps LoadRapport could be like this:
bool IsCompleted;
string LogText;
private async Task LoadRapport()
{
LogText = "Disable Replication";
IsCompleted = false;
_ = ShowStatus(); // start this task but don't wait for it to finish.
// Start these long running methods on a separate non UI thread using Task.Run.
await Task.Run(() => DisableReplication());
LogText = "Importing Weights";
await Task.Run(() => ImportWeights());
IsCompleted = true; // tell ShowStatus to complete.
}
and ShowStatus could be like this:
async Task ShowStatus()
{
while (!IsCompleted) {
BeginInvoke(new Action(() =>
{
UpdateProgressText(this.LogText);
})); ;
await Task.Delay(200);
}
UpdateProgressText("Completed");
}
I would also remember to disable the button that calls BtnGetRapportFile during this long running process so the user doesn't click it 10 times and get 10 separate threads running calling your SQL server...

.NET Async in shutdown methods?

I have an application that connects to a REST API using async methods. I have this set up using async/await pretty much everywhere that connects to the API, however I have a question and some strange behavior that I don't completely understand. What I want to do is simply return a license in certain scenarios when the program shuts down. This is initiated by a window closing event; the event handler is as follows:
async void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
...other synchronous code...
//Check for floating licensing
if (KMApplication.License != null && KMApplication.License.Scope != Enums.LicenseScope.Standalone)
{
for (int i = 0; i < 3; i++)
{
try
{
await KMApplication.License.ShutDown(KMApplication.Settings == null
? Enums.LicenseReturnModes.PromptOnShutdown
: KMApplication.Settings.LicenseReturnMode)
.ConfigureAwait(false);
break;
}
catch (Exception ex)
{
_logger.Warn("Exception in license release, attempt " + i, ex);
}
}
}
await KMApplication.ApiService.Disconnect().ConfigureAwait(false);
_logger.Info("Shutdown Complete");
Application.Current?.Shutdown();
}
When this runs I can step through in the debugger and it gets to the first license shutdown call which is the first async awaited call. Then when I press F10 to step to the next line of code it just shuts down and is gone. I verified that the license release that is supposed to be happening in that line is in face happening so it appears to run to completion of that line but then shuts down or crashes or something. I also looked at the logs and it never gets to the Shutdown Complete line and I don't believe it's getting to the ApiService.Disconnect either.
I also tried running this as a sync method using Task.Run(() => ...the method...).GetAwaiter().GetResult() but that just deadlocks on the first call.
How do I handle this and have it run the async release, wait for it to be done, then shut down?
The fundamental problem in what you're trying to do is that async/await assumes the main application thread continues running. This assumption directly conflicts with the shutdown action, whose job is to terminate all running tasks.
If you examine the documentation on Window_Closing, it states the following (and only the following):
Occurs directly after Close() is called, and can be handled to cancel window closure.
This is important. The only thing this is supposed to do is allow you to programmatically cancel the window closure, thus prompting some additional user action.
Your expectations are befuddled because of how async/await works. Async/await appears to run in a linear fashion; however, what actually happens is that control is passed back to the caller at the first await. The framework assumes at that point that you do not wish to cancel the form close, and the program is allowed to terminate, taking all other actions with it.
Fundamentally, all C-style programs have a main entry point, which runs a loop. It's been that way since the early days of C, and continues that way in WPF. However, in WPF, Microsoft got a bit clever, and decided to hide this from the programmer. There are a couple of options to deal with things that need to happen after main window closing:
Re-hijack the main loop from your program, and put the code there. The details on how to do this may be found here.
Set an explicit shutdown mode, and kick off the task to initiate that. Call Application.Shutdown() as the very last line of code you need to execute.
Here is an async version of the FormClosing event. It delays the closing of the form until the completion of the supplied Task. The user is prevented from closing the form before the completion of the task.
The OnFormClosingAsync event passes an enhanced version of the FormClosingEventArgs class to the handling code, with two additional properties: bool HideForm and int Timeout. These properties are read/write, much like the existing Cancel property. Setting HideForm to true has the effect of hiding the form while the async operation is in progress, to avoid frustrating the user. Setting Timeout to a value > 0 has the effect of abandoning the async operation after the specified duration in msec, and closing the form. Otherwise it is possible that the application could be left running indefinitely with a hidden UI, which could certainly be a problem. The Cancel property is still usable, and can be set to true by the handler of the event, to prevent the form from closing.
static class WindowsFormsAsyncExtensions
{
public static IDisposable OnFormClosingAsync(this Form form,
Func<object, FormClosingAsyncEventArgs, Task> handler)
{
Task compositeTask = null;
form.FormClosing += OnFormClosing; // Subscribe to the event
return new Disposer(() => form.FormClosing -= OnFormClosing);
async void OnFormClosing(object sender, FormClosingEventArgs e)
{
if (compositeTask != null)
{
// Prevent the form from closing before the task is completed
if (!compositeTask.IsCompleted) { e.Cancel = true; return; }
// In case of success allow the form to close
if (compositeTask.Status == TaskStatus.RanToCompletion) return;
// Otherwise retry calling the handler
}
e.Cancel = true; // Cancel the normal closing of the form
var asyncArgs = new FormClosingAsyncEventArgs(e.CloseReason);
var handlerTask = await Task.Factory.StartNew(
() => handler(sender, asyncArgs),
CancellationToken.None, TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default); // Start in a thread-pool thread
var hideForm = asyncArgs.HideForm;
var timeout = asyncArgs.Timeout;
if (hideForm) form.Visible = false;
compositeTask = Task.WhenAny(handlerTask, Task.Delay(timeout)).Unwrap();
try
{
await compositeTask; // Await and then continue in the UI thread
}
catch (OperationCanceledException) // Treat this as Cancel = true
{
if (hideForm) form.Visible = true;
return;
}
catch // On error don't leave the form hidden
{
if (hideForm) form.Visible = true;
throw;
}
if (asyncArgs.Cancel) // The caller requested to cancel the form close
{
compositeTask = null; // Forget the completed task
if (hideForm) form.Visible = true;
return;
}
await Task.Yield(); // Ensure that form.Close will run asynchronously
form.Close(); // Finally close the form
}
}
private struct Disposer : IDisposable
{
private readonly Action _action;
public Disposer(Action disposeAction) => _action = disposeAction;
void IDisposable.Dispose() => _action?.Invoke();
}
}
public class FormClosingAsyncEventArgs : EventArgs
{
public CloseReason CloseReason { get; }
private volatile bool _cancel;
public bool Cancel { get => _cancel; set => _cancel = value; }
private volatile bool _hideForm;
public bool HideForm { get => _hideForm; set => _hideForm = value; }
private volatile int _timeout;
public int Timeout { get => _timeout; set => _timeout = value; }
public FormClosingAsyncEventArgs(CloseReason closeReason) : base()
{
this.CloseReason = closeReason;
this.Timeout = System.Threading.Timeout.Infinite;
}
}
Since OnFormClosingAsync is an extension method and not a real event, it can only have a single handler.
Usage example:
public Form1()
{
InitializeComponent();
this.OnFormClosingAsync(Window_FormClosingAsync);
}
async Task Window_FormClosingAsync(object sender, FormClosingAsyncEventArgs e)
{
e.HideForm = true; // Optional
e.Timeout = 5000; // Optional
await KMApplication.License.ShutDown();
//e.Cancel = true; // Optional
}
The Window_FormClosingAsync handler will run in a thread-pool thread, so it should not include any UI manipulation code.
Unsubscribing from the event is possible, by keeping a reference of the IDisposable return value, and disposing it.
Update: After reading this answer, I realized that it is possible to add a real event FormClosingAsync in the form, without creating a class that inherits from the form. This can be achieved by adding the event, and then running an initialization method that hooks the event to the native FormClosing event. Something like this:
public event Func<object, FormClosingAsyncEventArgs, Task> FormClosingAsync;
public Form1()
{
InitializeComponent();
this.InitFormClosingAsync(() => FormClosingAsync);
this.FormClosingAsync += Window_FormClosingAsync_A;
this.FormClosingAsync += Window_FormClosingAsync_B;
}
Inside the initializer, in the internal handler of the native FormClosing event, all the subscribers of the event can be retrieved
using the GetInvocationList method:
var eventDelegate = handlerGetter();
if (eventDelegate == null) return;
var invocationList = eventDelegate.GetInvocationList()
.Cast<Func<object, FormClosingAsyncEventArgs, Task>>().ToArray();
...and then invoked appropriately. All this adds complexity, while the usefulness of allowing multiple handlers is debated. So I would probably stick with the original single-handler design.
Update: It is still possible to have multiple handlers using the original method OnFormClosingAsync. It is quite easy actually. The Func<T>
class inherits from Delegate, so it has invocation list like a real event:
Func<object, FormClosingAsyncEventArgs, Task> aggregator = null;
aggregator += Window_FormClosingAsync_A;
aggregator += Window_FormClosingAsync_B;
this.OnFormClosingAsync(aggregator);
No modification in the OnFormClosingAsync method is required.
Ok here is what I ended up doing. Basically the window closing kicks off a task that will wait for the release to happen and then invoke the shutdown. This is what I was trying to do before but it didn't seem to work in async void method but it seems to be when done this way. Here is the new handler:
void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
...other sync code...
Task.Run(async () =>
{
await InvokeKmShutdown();
(Dispatcher ?? Dispatcher.CurrentDispatcher).InvokeShutdown();
});
}
And the shutdown method looks like this:
async Task InvokeKmShutdown()
{
...other sync code...
await KMApplication.ApiService.Disconnect();
//Check for floating licensing
if (KMApplication.License != null && KMApplication.License.Scope != License.Core.Enums.LicenseScope.Standalone)
{
for (int i = 0; i < 3; i++)
{
try
{
await KMApplication.License.ShutDown(KMApplication.Settings == null
? Enums.LicenseReturnModes.PromptOnShutdown
: KMApplication.Settings.LicenseReturnMode);
break;
}
catch (Exception ex)
{
_logger.Warn("Exception in license release, attempt " + i, ex);
}
}
}
}
Hope it helps someone.
EDIT
Note that this is with an WPF app set to ShutdownMode="OnExplicitShutdown" in App.xaml so it won't shut down the actual app until I call the shutdown. If you are using WinForms or WPF is set to shut down on last window or main window close (main window close is the default I believe) you will end up with the race condition described in the comments below and may get the threads shut down before things run to completion.

Using ReactiveUI, Observables, SubscribeOn, and ObserveOn to display an output log in UI during a long-running process

I have a WPF application that will launch a long-running task (60+ seconds) that uses a System.Reactive.Subject<string> to push status messages periodically. The idea was that I could then Subscribe to the observable from my ViewModel and have ReactiveUI automatically update my UI through a Data Binding. This all works fine except that the TextBox is not updating in real-time. It only updates after long-running task has completed. I presume this is because my UI thread is being blocked and cannot update.
Working under that assumption, my research suggested that I could put the subscription on a background thread using SubscribeOn and then push the notifications back to the UI thread using ObserveOnDispatcher. However, this still did not produce the results that I wanted -- the UI only updated after long-running task had returned.
Can anybody give me some insight on what I need to do to allow my Output log to update in real time? Below are the related pieces of code.
XAML:
<TextBox Grid.Row="1" Text="{Binding Output}" IsReadOnly="True" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Margin="10,0,10,10" x:Name="OutputTextBox" />
Code-Behind:
protected override void OnContentRendered(EventArgs e)
{
if (Converter == null) return;
_viewModel = new ConversionOutputWindowViewModel(Converter);
DataContext = _viewModel;
_viewModel.StartConversion(); // Long-running Task
//_viewModel.StartSave();
FinishButton.IsEnabled = true;
}
ViewModel:
private string _output;
public string Output // Data bound in XAML
{
get { return _output; }
set { this.RaiseAndSetIfChanged(ref _output, value); }
}
public void StartConversion()
{
_edmxConverter.Convert(); // Long-running Task
}
public ConversionOutputWindowViewModel(Utilities.Converters.EdmxConverter converter)
{
_edmxConverter = converter;
_compositeDisposable.Add(_edmxConverter.Output
.SubscribeOn(NewThreadScheduler.Default)
.ObserveOnDispatcher()
.Subscribe(s => Output = Output += s));
//_compositeDisposable.Add(_edmxConverter.Output.Subscribe(s => Output = Output += s));
}
Long-Running Task Function:
public Subject<string> Output { get; }
Output = new Subject<string>(); //In ctor
private void PrintReplacement(XAttribute attribute, string oldValue, string newValue, int level, Verbosity minVerbosity = Verbosity.Informational)
{
if (Verbosity < minVerbosity) return;
Output.OnNext($"{new string('\t', level)}{attribute.Name}: {oldValue} -> {newValue}{Environment.NewLine}");
}
Would it maybe help to wrap my Long-running Task function call inside an await Task.Run? I'm grasping at straws here. I don't have a very good working knowledge of .NET threading.
Well you are using subjects, so .SubscribeOn(NewThreadScheduler.Default) does nothing. (Standard old drum beating) Don't use subjects.
Your long running process should be a method call that returns an IObservable<T> (instead of having a class that exposes a property). The T should be the status updates you want to receive. When the task is done, then you OnComplete. If the task fails, you OnError.
Ideally the method that returns the IObservable using Observable.Create to deifne the work that will need to be done.
public IObservable<string> Convert()
{
return Observable.Create<string>(observer=>
{
//do stuff here.
//Call observer.OnNext with status messages
//When done call observer.OnCompleted()
});
}
As you dont show what your long running task is I cant help anymore with the implementation.

UI freezes when using async await

I have troubles to make my UI work using an async method. Here is a part of my code
private async void btnDoOutput_Click(object sender, RoutedEventArgs e)
{
/* Initiliaze */
groupBoxConfiguration.IsEnabled = false;
var progressIndicator = new Progress<int>();
progressIndicator.ProgressChanged += (s,value) =>
{
progressExport.Value = (double)value;
labelPercentage.Content = "Export in progress : " + value + " %";
};
/* do Work */
switch (something)
{
case 1:
await method(input, output, options, progressIndicator);
break;
default: break;
}
/* Finalization */
groupBoxConfiguration.IsEnabled = true;
}
The method is
public async static Task<string> method(string input, string output, string options, IProgress<int> progress)
{
while(something)
{
//operations on input and output
if (progress != null)
{
progress.Report(percentage);
}
}
}
When I click on my button, the UI freezes, the groupBox is still enabled, the progress is not shown until the end.
I think you are completely misunderstanding how async / await actually works. All of your code is still running on the UI thread because you don't actually tell it otherwise. This means your await on method is pointless because it's going to run synchronously anyway.
The purpose of async/await is to allow the calling code the opportunity to continue processing until it hits part of the code that requires the result of the awaitable task. So in your example, you would need to change your method body to actually return an awaitable Task
public Task method(string input, string output, string options, IProgress<int> progress)
{
return Task.Run(() => {
while(something)
{
//operations on input and output
if (progress != null)
{
progress.Report(percentage);
}
}
});
}
First of all please don't use static methods, especially static workers.
I believe the problem is you're still on your UI thread (I'm making some wild assumptions based on the code you've given). Try using Task<string>.Factory.StartNew(...) which should automatically invoke off the UI thread.
Note may need to use the dispatcher and invoke back onto the UI thread to get your Progress bar working.

WPF/C# Don't block the UI

I've an existing WPF application, which has several sections. Every section is a UserControl, that implements an interface.
The interface specify two methods: void LoadData([...]) and bool UnloadData().
Those method are called by the UI thread, so we need to do our work in backgroundworker if it's time consuming.
No problems with LoadData since we can update the UI asynchronously. The problem is with UnloadData().
This should return if we can really leave the current view.
This is computed with the current status of data(Saved/modified/Invalid):
Saved return true,
Invalid asks if you want to stay to save some
correct data or leave without saving
Modified tell you that you can
either cancel your change(return true), either continue to
edit(return false), either save you current data(return true)
The problem is with the "Modified -> Save". This is a time consuming method, so to respect the philosophy of the application, we should run this in a background thread(with a busy indicator).
But if we just launch the thread and go to the next section, it will return "true" to the method call, and we will directly launch the next view.
In my case, loading the next view before our local data is saved can be a problem.
So:
Is there a way to wait on the background thread to finish before returning "true", WITHOUT blocking the UI?
public bool UnloadData(){
if(...){
LaunchMyTimeConsumingMethodWithBackgroundWorker();
return true;//Only when my time consuming method ends
}
//[...]
}
Important EDIT
Maybe I wasn't clear enought: I know how to use a BackgroundWorker, or TPL. My problem is that the parent class(the one which call the UnloadData()" is a class that I cannot edit(for multiple reasons: It's in another DLL that will not be reloaded, it already works with 70+ userControls, all in separate projects(dll), loaded by reflection.
This wasn't my choice, I don't find it good, but I've to deal with it now. I'm mostly looking for way to make my method wait on the return of my method. I'm not sure if it is possible. But I'm looking for a workaround, it will spare me weeks of works.
Ok now I'm excited, because I think I may have discovered something on my own...
So, what you do is this: You create a DispatcherFrame, push that frame onto the Dispatcher, and in the RunWorkerCompleted you set the Continue of the Frame to false.
This is the code so far:
public void Function()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += TimeConsumingFunction;
var frame = new DispatcherFrame();
worker.RunWorkerCompleted += (sender, args) =>
{
frame.Continue = false;
};
worker.RunWorkerAsync();
Dispatcher.PushFrame(frame);
}
private void TimeConsumingFunction(object sender, DoWorkEventArgs doWorkEventArgs)
{
Console.WriteLine("Entering");
for (int i = 0; i < 3; i++)
{
Thread.Sleep(1000);
}
Console.WriteLine("Exiting");
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Function();
Console.WriteLine("Returns");
}
You should implement a dependency property "IsBusy" of type bool, that you set to TRUE before starting the BackgoundWorker, and then to FALSE when the work is complete.
On the UI, you bind to that property whatever functionality you want disabled during the processing(like the button for loading the next view, etc.); or maybe showing a "Cancel" button.
You should not "wait" for the operation to complete, you can retrieve the result in an additional variable, that the BackgroundWorker will set:
BackgroundWorker _bw;
bool _returnValue = false;
private void button_Click(object sender, RoutedEventArgs e)
{ // if starting the processing by clicking a button
_bw = new BackgroundWorker();
IsBusy = true;
_bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
_bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bw_RunWorkerCompleted);
_bw.RunWorkerAsync();
}
void _bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
IsBusy = false;
// retrieve the result of the operation in the _returnValue variable
}
void _bw_DoWork(object sender, DoWorkEventArgs e)
{
_returnValue = UnloadData();
}
private bool UnloadData()
{
if (...)
{
LaunchTimeConsumingMethod();
return true;
}
else
return false;
//etc ...
}
public bool IsBusy
{
get { return (bool)GetValue(IsBusyProperty); }
set { SetValue(IsBusyProperty, value); }
}
// Using a DependencyProperty as the backing store for IsBusy. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsBusyProperty =
DependencyProperty.Register( ... )
You may be able to try using the new "await" features of .NET 4.5.
The await keyword allows you to await the completion of a Task object, without blocking the UI.
Try this modification:
public async bool UnloadData()
{
if(...)
{
await Task.Factory.StartNew(() =>
{
LaunchMyTimeConsumingMethod();
});
return true;//Only when my time consuming method ends
}
//[...]
}
Treat UnloadData as a async operation and let the async/await features handle both the case when it completes synchronously and when it needs to complete asynchronously:
public async Task<bool> UnloadData(){
if(...){
// The await keyword will segment your method execution and post the continuation in the UI thread
// The Task.Factory.StartNew will run the time consuming method in the ThreadPool
await Task.Factory.StartNew(()=>LaunchMyTimeConsumingMethodWithBackgroundWorker());
// The return statement is the continuation and will run in the UI thread after the consuming method is executed
return true;
}
// If it came down this path, the execution is synchronous and is completely run in the UI thread
return false;
}
private async void button_Click(object sender, RoutedEventArgs e)
{
// Put here your logic to prevent user interaction during the operation's execution.
// Ex: this.mainPanel.IsEnabled = false;
// Or: this.modalPanel.Visibility = Visible;
// etc
try
{
bool result = await this.UnloadData();
// Do whatever with the result
}
finally
{
// Reenable the user interaction
// Ex: this.mainPanel.IsEnabled = true;
}
}
EDIT
If you can't modify the UnloadData, then just execute it on the ThreadPool, as #BTownTKD noted:
private async void button_Click(object sender, RoutedEventArgs e)
{
// Put here your logic to prevent user interaction during the operation's execution.
// Ex: this.mainPanel.IsEnabled = false;
// Or: this.modalPanel.Visibility = Visible;
// etc
try
{
// The await keyword will segment your method execution and post the continuation in the UI thread
// The Task.Factory.StartNew will run the time consuming method in the ThreadPool, whether it takes the long or the short path
bool result = await The Task.Factory.StartNew(()=>this.UnloadData());
// Do whatever with the result
}
finally
{
// Reenable the user interaction
// Ex: this.mainPanel.IsEnabled = true;
}
}
You probably should use TPL if your framework version is 4.0:
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); // this will work only if you're running this code from UI thread, for example, by clicking a button
Task.Factory.StartNew(() => UnloadData()).ContinueWith(t => /*update ui using t.Result here*/, uiScheduler);
Hope this helps.
You have to implement a callback function (RunWorkerCompleted), this is called when the background worker finishes.
Check out an example here:
http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx

Categories

Resources