By lack of any response on the developer's GitHub I will repeat my question here.
I hope someone can help me in some way.
This is the first time I am using MvvmLight, so I hope I am not overlooking something obvious.
In my WPF ViewModel I have something like:
private ICommand readFileCommand;
public ICommand ReadFileCommand => readFileCommand ?? (readFileCommand = new RelayCommand(ReadFile));
private void ReadFile()
{
FileMessage = "Message.";
}
private string fileMessage;
public string FileMessage
{
get { return fileMessage; }
set
{
//Set(ref fileMessage, value);
fileMessage = value;
RaisePropertyChanged();
}
}
I have a couple of problems with it.
Main problem is that setting a property like FileMessage from within a method like ReadFile() does not result in an update of the view until ReadFile is completed.
There is a difference between using RaisePropertyChanged() which succeeds at that moment, and using Set() which does nothing at all. Though the latter did work outside such a method.
The problem extends to other elements like a DataGrid on a DataView.
In wondered if the called methods should be asynchronous, but that does not seem logical. I have not tried that yet as that does not really fit into what I want to achieve.
So what is happening? Am I overlooking something? Is this a limitation of the framework? Or is this a bug?
Thanks!
Main problem is that setting a property like FileMessage from within a method like ReadFile() does not result in an update of the view until ReadFile is completed.
This makes sense as you cannot both execute your ReadFile method and update the UI on the same thread simultaneously. This has nothing to do with MvvmLight or commands.
If you set the property before you run any potentially long-running code, either asynchronously or synchronously on a background thread, it should work as expected.
Try this for example:
private async void ReadFile()
{
FileMessage = "Message.";
await Task.Delay(5000); //simulate...
FileMessage = "Done!";
}
Or this:
private async void ReadFile()
{
FileMessage = "Message.";
await Task.Run(() => Thread.Sleep(5000));
FileMessage = "Done!";
}
I 'solved' this the way below, which does for me what I ultimately wanted, but still leaves me with questions.
The code is sort of a complete example to make my point.
One remark. Using the debugger with breaks can be very misleading in the behaviour.
Observations
What DOES work are the 'Run' messages.
However, all of the other messages are not displayed. And that leaves me dumbfounded.
The ReadFile method is synchronous and directly on the UI thread (I checked by the name), and does nothing with threading outside of the Task.
So why is it unable to display anything? I even superfluously used Invoke, which did not help.
So that still leaves me doubting whether the RelayCommand properly works.
I even considered switching to Windows Community Toolkit, but that seemed too big a step.
It may end there anyway, as MVVVM Light is already dropped by Windows Template Studio, and development stopped december 2018!
If I overlook anything, clarifications are still appreciated.
using GalaSoft.MvvmLight;
using Application.Contracts.ViewModels;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
namespace ViewModels
{
public class ViewModel : ViewModelBase, INavigationAware
{
public ViewModel()
{
uiDispatcher = Dispatcher.CurrentDispatcher;
}
private Dispatcher uiDispatcher;
public async void OnNavigatedTo(object parameter)
{
uiDispatcher.Thread.Name = "OnNavigatedTo";
}
public void OnNavigatedFrom()
{ }
private string fileMessage = "No file yet";
public string FileMessage
{
get { return fileMessage; }
set
{
// Using this simple way instead of Set, which did not work.
fileMessage = value;
RaisePropertyChanged();
}
}
private void ReadFile()
{
FileMessage = "ReadFile 1.";
Thread.Sleep(1000);
uiDispatcher.Invoke(() => FileMessage = "ReadFile Invoke 1.", DispatcherPriority.Send);
Thread.Sleep(1000);
// Use Run on a non UI-thread and Dispatcher to enable intermediate updates back on the UI-thread.
Task.Run(() =>
{
uiDispatcher.Invoke(() => FileMessage = "Run 1.", DispatcherPriority.Send);
Thread.Sleep(1000);
uiDispatcher.Invoke(() => FileMessage = "Run 2.", DispatcherPriority.Send);
Thread.Sleep(1000);
});
Thread.Sleep(1000);
FileMessage = "ReadFile 2.";
Thread.Sleep(1000);
uiDispatcher.Invoke(() => FileMessage = "ReadFile Invoke 2.", DispatcherPriority.Send);
}
}
}
Try a slightly modified implementation:
private async void ReadFile()
{
FileMessage = "ReadFile 1.";
await Task.Delay(1000);
await uiDispatcher.BeginInvoke(() => FileMessage = "ReadFile Invoke 1.", DispatcherPriority.Send);
await Task.Delay(1000);
// Use Run on a non UI-thread and Dispatcher to enable intermediate updates back on the UI-thread.
await Task.Run(async () =>
{
uiDispatcher.Invoke(() => FileMessage = "Run 1.", DispatcherPriority.Send);
await Task.Delay(1000);
uiDispatcher.Invoke(() => FileMessage = "Run 2.", DispatcherPriority.Send);
await Task.Delay(1000);
});
await Task.Delay(1000);
FileMessage = "ReadFile 2.";
await Task.Delay(1000);
await uiDispatcher.BeginInvoke(() => FileMessage = "ReadFile Invoke 2.", DispatcherPriority.Send);
}
}
In general, this code is absurd.
But I didn’t make any big changes to maintain continuity.
P.S. The code was written here in the post editor.
Sorry - minor mistakes are possible.
Related
I have tried the code mentioned in this answer: https://stackoverflow.com/a/27089652/
It works fine and I want to use it for running a PowerShell script in for loop. GUI was freezing initially then I tried the code mentioned in this answer: https://stackoverflow.com/a/35735760/
Now GUI does not freeze while the PowerShell script is running in the background although nothing is updated in the textbox until for loop is complete. I want to see the results updating in real time. Here is the code I am running:
private async void run_click(object sender, RoutedEventArgs e)
{
Text1.Text = "";
await Task.Run(() => PS_Execution(Text1));
}
internal async Task PS_Execution(TextBox text)
{
PowerShell ps = PowerShell.Create();
ps.AddScript(script.ToString());
{
Collection<PSObject> results = ps.Invoke();
foreach (PSObject r in results)
{
text.Dispatcher.Invoke(() =>
{
text.Text += r.ToString();
});
await Task.Delay(100);
}
}
}
Maybe I am missing something important. Please help me understand how to solve this problem.
Instead of using ps.Invoke() which is synchronous call and will wait for all results to return use ps.BeginInvoke() instead. Then subscribe to the DataAdded event of the output PSDataCollection and use the action to update your ui.
private async void run_click(object sender, RoutedEventArgs e)
{
Text1.Text = "";
await Task.Run(() => PS_Execution(Text1));
}
internal async Task PS_Execution(TextBox text)
{
using PowerShell ps = PowerShell.Create();
ps.AddScript(script.ToString());
PSDataCollection<string> input = null;
PSDataCollection<string> output = new();
IAsyncResult asyncResult = ps.BeginInvoke(input, output);
output.DataAdded += (sender, args) =>
{
var data = sender as PSDataCollection<string>;
text.Dispatcher.Invoke(() =>
{
text.Text += data[args.Index];
});
};
}
Hi I want to use the ObservableCollection (AddRange) in async Task but i get NotSupportedException
private ObservableCollection<CoronavirusCountry> _data = new ObservableCollection<CoronavirusCountry>();
public ObservableCollection<CoronavirusCountry> data
{
get => _data;
set => SetProperty(ref _data, value);
}
Task.Run(async()=>{
APIService service = new APIService();
data.AddRange(await service.GetTopCases());
Status = "Updated " + DateTime.Now;
});
Not sure which AddRange method you are referring to, because ObservableCollection doesn't have that out of the box.
Anyway - assuming you wrote an extension method - it has to be called in the UI thread, so running a Task doesn't make sense.
The awaitable method shown below should be sufficient. It would await the asynchronous service call, and update the collection in the main thread.
public async Task UpdateData()
{
var service = new APIService();
var newData = await service.GetTopCases();
Data.AddRange(newData); // use proper naming!
Status = "Updated " + DateTime.Now;
}
In order to call and await the above method, you could have an async Loaded event handler like this:
public MainWindow()
{
InitializeComponent();
viewModel = new ViewModel();
Loaded += async (s, e) => await viewModel.UpdateData();
}
We're having a winforms application that uses an async initialization process. Simplified you can say that the application will run the following steps:
Init - this runs async
Show MainForm
Application.Run()
The currently existing and working code looks like this:
[STAThread]
private static void Main()
{
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
var task = StartUp();
HandleException(task);
Application.Run();
}
private static async Task StartUp()
{
await InitAsync();
var frm = new Form();
frm.Closed += (_, __) => Application.ExitThread();
frm.Show();
}
private static async Task InitAsync()
{
// the real content doesn't matter
await Task.Delay(1000);
}
private static async void HandleException(Task task)
{
try
{
await Task.Yield();
await task;
}
catch (Exception e)
{
Console.WriteLine(e);
Application.ExitThread();
}
}
The background how this is working is described very detailed by Mark Sowul here.
Since C# 7.1 we're able to use async Task in main method. We tried it in a straight forward way:
[STAThread]
private static async Task Main()
{
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
try
{
await StartUp();
Application.Run();
}
catch (Exception e)
{
Console.WriteLine(e);
Application.ExitThread();
}
}
private static async Task StartUp()
{
await InitAsync();
var frm = new Form();
frm.Closed += (_, __) => Application.ExitThread();
frm.Show();
}
private static async Task InitAsync()
{
// the real content doesn't matter
await Task.Delay(1000);
}
But that doesn't work. The reason is clear. All the code after the first await will be forwarded to the message loop. But the message loop hasn't startet yet because the code that starts it (Application.Run()) is located after the first await.
Removing the synchronization context will fix the problem but causes to run the code after await in a different thread.
Reordering the code to call Application.Run() before the first await will not work because it is a blocking call.
We try to use the new feature of having an async Task Main() that allows us to remove the HandleException-solution that is hard to understand. But we don't know how.
Do you have any suggestions?
You don't need async Main. Here is how it can possibly be done:
[STAThread]
static void Main()
{
void threadExceptionHandler(object s, System.Threading.ThreadExceptionEventArgs e)
{
Console.WriteLine(e);
Application.ExitThread();
}
async void startupHandler(object s, EventArgs e)
{
// WindowsFormsSynchronizationContext is already set here
Application.Idle -= startupHandler;
try
{
await StartUp();
}
catch (Exception)
{
// handle if desired, otherwise threadExceptionHandler will handle it
throw;
}
};
Application.ThreadException += threadExceptionHandler;
Application.Idle += startupHandler;
try
{
Application.Run();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
Application.Idle -= startupHandler;
Application.ThreadException -= threadExceptionHandler;
}
}
Note, if you don't register threadExceptionHandler event handler and StartUp throws (or anything else on the message loop throws, for the matter), it will still work. The exception will be caught inside the try/catch which wraps Application.Run. It will just be a TargetInvocationException exception with the original exception available via its InnerException property.
Updated to address the comments:
But for me it looks very strange to register an EventHandler to the
idle event so startup the whole application. It's totally clear how
that works but still strange. In that case I prefer the
HandleException solution that I already have.
I guess it's a matter of taste. I don't know why WinForms API designers didn't provide something like WPF's Application.Startup. However, in the lack of a dedicated event for this on WinForm's Application class, deferring specific initialization code upon the first Idle event is IMO an elegant solution, and it's widely used here on SO.
I particularly don't like the explicit manual provisioning of WindowsFormsSynchronizationContext before Application.Run has started, but if you want an alternative solution, here you go:
[STAThread]
static void Main()
{
async void startupHandler(object s)
{
try
{
await StartUp();
}
catch (Exception ex)
{
// handle here if desired,
// otherwise it be asynchronously propogated to
// the try/catch wrapping Application.Run
throw;
}
};
// don't dispatch exceptions to Application.ThreadException
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
using (var ctx = new WindowsFormsSynchronizationContext())
{
System.Threading.SynchronizationContext.SetSynchronizationContext(ctx);
try
{
ctx.Post(startupHandler, null);
Application.Run();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
System.Threading.SynchronizationContext.SetSynchronizationContext(null);
}
}
}
IMO, either approach is more clean than the one used in your question. On a side note, you should be using ApplicationContext to handle the form closure. You can pass an instance of ApplicationContext to Application.Run.
The only point that I'm
missing is your hint that the synchronization context is already set.
Yes it is - but why?
It is indeed set as a part of Application.Run, if not already present on the current thread. If you like to learn more details, you could investigate it in .NET Reference Source.
I have a ReactiveCommand that refreshes data and is bound to a Button in XAML. The functionality works fine, but I also want to execute the command on a timer.
I have the following code - SetupAutoRefresh is called from the ctor in my VM, but when the Observable fires, I get an exception with the message: "The calling thread cannot access this object because a different thread owns it."
VM:
private void SetupAutoRefresh() {
Observable.Timer(TimeSpan.FromSeconds(5))
.Select(_ => Unit.Default)
.ObserveOn(RxApp.MainThreadScheduler)
.InvokeCommand(RefreshData);
RefreshData = ReactiveCommand.CreateFromTask(Refresh);
}
private async Task Refresh()
{
var updatedData = await _repository.GetAll();
Data.Merge(updatedData);
}
private ReactiveCommand<Unit, Unit> _refreshData;
public ReactiveCommand<Unit, Unit> RefreshData
{
get { return _refreshData; }
set { this.RaiseAndSetIfChanged(ref _refreshData, value); }
}
private IReactiveList<Model> _data;
public IReactiveList<Model> Data
{
get { return _data; }
set { this.RaiseAndSetIfChanged(ref _data, value); }
}
XAML:
<Button Grid.Column="2"
Command="{Binding RefreshData}"
Style="{StaticResource ToolbarButtonTheme}"
Content="{StaticResource RefreshToolbarIcon}"
ToolTip="Refresh Search"/>
Debug output provides this stacktrace:
at System.Windows.Threading.Dispatcher.VerifyAccess()
at System.Windows.DependencyObject.GetValue(DependencyProperty dp)
at System.Windows.Controls.Primitives.ButtonBase.get_Command()
at System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute()
at System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged(Object >sender, EventArgs e)
at
System.Windows.Input.CanExecuteChangedEventManager.HandlerSink.OnCanExecuteChanged(Object sender, EventArgs e)
at ReactiveUI.ReactiveCommand.OnCanExecuteChanged() in C:\projects\reactiveui\src\ReactiveUI\ReactiveCommand.cs:line 628
I've tried many different variations of attempting to schedule this on the RxApp.MainThreadScheduler but without any joy - ObserveOn, SubscribeOn, setting the output scheduler... none of which I had much hope for anyway.
Feel like I'm missing something obvious here, but have been banging my head against a brick wall for the whole afternoon. Surely this scenario is possible in RxUI?
The Refresh method runs on a background thread; you can't modify databound properties within that method.
Try this:
private void SetupAutoRefresh() {
Observable.Timer(TimeSpan.FromSeconds(5))
.Select(_ => Unit.Default)
// remove ObserveOn here; the Command will run on the background
.InvokeCommand(RefreshData);
RefreshData = ReactiveCommand.CreateFromTask(Refresh);
// RefreshData.Subscribe is guaranteed to run on the UI thread
RefreshData.Subscribe(listOfModels => Data.Merge(listOfModels))
}
private async Task Refresh()
{
// all this method does is deliver a list of models
return await _repository.GetAll();
}
// return IEnumerable<Model> from the command
public ReactiveCommand<Unit, IEnumerable<Model>> RefreshData
Now, your ReactiveCommand simply fetches the new data, and returns it to you on the UI thread within Subscribe :)
Figured out the issue - looks like the Observable needed to be created on the UI thread. I missed it from the original post, but the SetupAutoRefresh method had been called from another async method, which had switched context during a prior await.
I am having problem with the application freezing. Let me explain my scenario, I have a service which does an async call to a database to get a list of items, It is run by a task. Inside this task I have a try catch block, so it looks like this
public Task<List<T>> ComboListAsync(int? id = null, EnumDTO dto = EnumDTO.Default)
{
return Task.Run(() =>
{
using (var context = new ContextService())
{
try
{
return GetComboList(id, dto, context);
}
catch (Exception e)
{
Handler.DatabaseConnectionException();
throw;
}
}
});
}
Then it throws an exception as GetComboList its just this (for the moment)
protected virtual List<T> GetComboList(int? id, EnumDTO dto, ContextService context)
{
throw new NotImplementedException();
}
So the call catches the exception and goes inside here
public void Show(string message)
{
Message = message;
Application.Current.Dispatcher.Invoke(() =>
{
dialogView = new DialogView() {DataContext = this, Owner = Application.Current.MainWindow};
dialogView.ShowDialog();
});
}
Now the Dispatcher freezes the app, I tried to change it to use begin invoke, it does the same. Without the dispatcher I get an error message that the calling thread is not a STA. I simply want to display my message in a dialog window, that there was a problem connecting to a database. Can anyone help?
I looked online and there is many threads about dispatcher, but none actually show a solution that will fix my issue.
Thank you
EDIT
Code which calls the ComboListAsync
protected override void RetrieveRelatedActiveLists()
{
MyCollection = service.ComboListAsync().Result;
}
Its a deadlock because of the calling code is using the .Result.
Using service.ComboListAsync().Result makes the UI thread await for this method to return, when you call Application.Current.Dispatcher.Invoke from within it you are sending a message to the UI thread that is awaiting the return of method itself.
You must await the method service.ComboListAsync() like this:
protected override async void RetrieveRelatedActiveLists()
{
MyCollection = await service.ComboListAsync();
}