I am trying to update property on my main Thread, which is bind to ProgressBar. In viewmodel I have the bellow code, which is not working.
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task task = Task.Factory.StartNew(() =>
{
DoLongRunningWork();
}).ContinueWith(_=>
{
ApplicationStatus = "Task finished!";
}, TaskScheduler.FromCurrentSynchronizationContext());
DoLongRunningWork()
{
// Alot of stuff
Task.Factory.StartNew(() =>
{
ProgressBarValue += progressTick;
}).Start(uiScheduler);
}
If the property ProgressBarValue is bound to a WPF element, then the only thread that can update the ProgressBar is the very thread that created it.
So, my assumption is that the class that contains ProgressBarValue also implements INotifyPropertyChanged. This means that you have some logic that raises the event PropertyChanged.
I would create a method that raises the event, and always does so using the Dispatcher. (The Dispatcher allows you to call functions on the thread that created your WPF controls.)
private void raisePropertyChanged(string name)
{
Dispatcher.InvokeAsync(()=>
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
});
}
This will always update the ProgressBar on the proper thread.
Related
I created some test code so I could try and figure out how to use multiple windows in UWP properly. I wanted to see if I could fire an event and have multiple windows update their UI in the event handler. I finally got something working but I'm not entirely sure why it works.
Here's the class that's being created in my Page
public class NumberCruncher
{
private static Dictionary<int, Tuple<CoreDispatcher, NumberCruncher>> StaticDispatchers { get; set; }
static NumberCruncher()
{
StaticDispatchers = new Dictionary<int, Tuple<CoreDispatcher, NumberCruncher>>();
}
public NumberCruncher()
{
}
public event EventHandler<NumberEventArgs> NumberEvent;
public static void Register(int id, CoreDispatcher dispatcher, NumberCruncher numberCruncher)
{
StaticDispatchers.Add(id, new Tuple<CoreDispatcher, NumberCruncher>(dispatcher, numberCruncher));
}
public async Task SendInNumber(int id, int value)
{
foreach (var dispatcher in StaticDispatchers)
{
await dispatcher.Value.Item1.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Debug.WriteLine($"invoking {dispatcher.Key}");
dispatcher.Value.Item2.NumberEvent?.Invoke(null, new NumberEventArgs(id, value));
});
}
}
}
And here's the relevant part of my MainPage code
NumberCruncher numberCruncher;
public MainPage()
{
this.InitializeComponent();
numberCruncher = new NumberCruncher();
numberCruncher.NumberEvent += NumberCruncher_NumberEvent;
}
private async void NumberCruncher_NumberEvent(object sender, NumberEventArgs e)
{
listView.Items.Add($"{e.Id} sent {e.Number}");
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
NumberCruncher.Register(ApplicationView.GetForCurrentView().Id, Window.Current.Dispatcher, numberCruncher);
}
I have a button that creates new views of the MainPage. Then I have another button that calls the SendInNumber() method.
When I navigate to the MainPage I register the Dispatcher for the window and the instance of NumberCruncher. Then when firing the event I use the NumberCruncher EventHandler for that specific Dispatcher.
This works without throwing marshaling exceptions. If I try to use the current class's EventHandler
await dispatcher.Value.Item1.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Debug.WriteLine($"invoking {dispatcher.Key}");
NumberEvent?.Invoke(null, new NumberEventArgs(id, value));
});
I get a marshaling exception when trying to add the item to the listView. However if I maintain the SynchronizationContext in my MainPage and then use SynchronizationContext.Post to update the listView. It works fine
SynchronizationContext synchronizationContext;
public MainPage()
{
this.InitializeComponent();
numberCruncher = new NumberCruncher();
numberCruncher.NumberEvent += NumberCruncher_NumberEvent;
synchronizationContext = SynchronizationContext.Current;
}
private async void NumberCruncher_NumberEvent(object sender, NumberEventArgs e)
{
synchronizationContext.Post(_ =>
{
listView.Items.Add($"{e.Id} sent {e.Number}");
}, null);
}
However this does not work and throws a marshaling exception when trying to update listView.
private async void NumberCruncher_NumberEvent(object sender, NumberEventArgs e)
{
await CoreApplication.GetCurrentView().CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
listView.Items.Add($"{e.Id} sent {e.Number}");
});
}
What is going on here?
Important thing to remember is that when an event is fired, the subscribed methods are called on the same thread as the Invoke method.
If I try to use the current class's EventHandler, I get a marshaling exception when trying to add the item to the listView.
This first error happens because you are trying to fire the event of current class on another Window's dispatcher (dispatcher.Value.Item1). Let's say the event is on Window 1 and the dispatcher.Value.Item1 belongs to Window 2. Once inside the Dispatcher block, you are or the UI thread of Window 2 and firing the NumberEvent of Window 1's NumberCruncher will run the Window 1's handler on the Window 2's UI thread and that causes the exception.
However this does not work and throws a marshaling exception when trying to update listView.
The GetCurrentView() method returns the currently active view. So whichever application view is active at that moment will be the one returned. In your case it will be the one on which you clicked the button. In case you call the Invoke method on the target Window's UI thread, you don't need any additional code inside the NumberEvent handler.
I have a WPF MVVM app under NET 3.5 and Visual Studio 2008. Some controls in the view are bound to properties on the view model, so when these properties changes it will be notified to the view through the implementation of INotifyPropertyChanged.
I have some kind of splash saying "Loading..." that appears in the center of the window at the beginning, and it keeps visible while some data is being requested from database. Once data is requested from database, I want to hide this splash.
This splash is bound to a propert, "IsSplashVisible" in view model so updating the property to true, notify the splash to be shown at the beginnig and setting it to false, notify the splash to be hidden.
Setting property "IsSplashVisible" to true at the beginning there is no problem, the problem appears when setting the property to false once queued work item finishes. Once set this property to false, control (splash "Loading...") is notified and it tries to hide but fails as this is a different thread that the one who created it so the typical exception is thrown. So how can I solve this?
Below the code.
View model:
public class TestViewModel : BaseViewModel
{
private static Dispatcher _dispatcher;
public ObservableCollection<UserData> lstUsers
public ObservableCollection<UserData> LstUsers
{
get
{
return this.lstUsers;
}
private set
{
this.lstUsers= value;
OnPropertyChanged("LstUsers");
}
}
private bool isSplashVisible = false;
public bool IsSplashVisible
{
get
{
return this.isSplashVisible;
}
set
{
if (this.isSplashVisible== value)
{
return;
}
this.isSplashVisible= value;
OnPropertyChanged("IsSplashVisible");
}
}
public TestViewModel()
{
this.IsSplashVisible = true;
ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
{
var result = getDataFromDatabase();
UIThread(() =>
{
LstUsers = result;
this.IsSplashVisible = false; <---- HERE IT FAILS
});
}));
}
ObservableCollection<UserData> getDataFromDatabase()
{
return this.RequestDataToDatabase();
}
static void UIThread(Action a)
{
if(_dispatcher == null) _dispatcher = Dispatcher.CurrentDispatcher;
//this is to make sure that the event is raised on the correct Thread
_dispatcher.Invoke(a); <---- HERE EXCEPTION IS THROWN
}
}
Dispatcher.CurrentDispatcher is not the Dispatcher of the UI thread, because it
Gets the Dispatcher for the thread currently executing and creates a new Dispatcher if one is not already associated with the thread.
You should use the Dispatcher of the current Application instance:
ThreadPool.QueueUserWorkItem(o =>
{
var result = getDataFromDatabase();
Application.Current.Dispatcher.Invoke(() =>
{
LstUsers = result;
IsSplashVisible = false;
});
});
Assuming that your TestViewModel constructor is called in the UI thread, you could have written it like shown below, where Dispatcher.CurrentDispatcher is called in the UI thread instead of a ThreadPool thread. However, the field is entirely redundant. You could always just call Application.Current.Dispatcher.Invoke().
public class TestViewModel
{
private readonly Dispatcher _dispatcher = Dispatcher.CurrentDispatcher;
public TestViewModel()
{
IsSplashVisible = true;
ThreadPool.QueueUserWorkItem(o =>
{
var result = getDataFromDatabase();
_dispatcher.Invoke(() =>
{
LstUsers = result;
IsSplashVisible = false;
});
});
}
...
}
Recently, I've been testing with binding of type which implements INotifyPropertyChanged and updating property from worker thread throwing cross thread issue.
Here is sample code :
public class MyViewModel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler hanlder = PropertyChanged;
if(hanlder != null)
hanlder(this, new PropertyChangedEventArgs(propertyName));
}
}
above viewmodel has been bind with label text in windows form and updating label value from worker thread.
Updating label1 text from worker thread causes cross thread issue :
public partial class MainForm : Form
{
private MyViewModel _myViewModel = new MyViewModel();
public MainForm()
{
InitializeComponent();
Btn1.Click += Btn1_Click;
label1.DataBindings.Add("Text", _myViewModel, "Name");
}
private void Btn1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
_myViewModel.Name = "Updating from worker thread!"; It causes cross thread issue
});
}
}
So far, I can believe is that it is due to updating UI from worker thread. Is there any work around to make it thread safe without changing in button click method i.e. probably making thread safe in viewmodel.
Grab the UI's SynchronizationContext (using SynchronizationContext.Current from the UI thread when the app starts, for example), and store it in some static variable somewhere (i've called it uiSynchronizationContext).
Then on your OnPropertyChanged do something like:
protected virtual void OnPropertyChanged(string propertyName)
{
uiSynchronizationContext.Post(
o => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName))
,null
);
}
Or you can use Send instead of Post if you want that operation synchronous (synchronous to the thread that started the send/post operation, it'll always be "synchronous" on the UI thread)
I particulary don't like doing direct databinding when multithreading (I prefer to poll with a timer on the UI thread from some graph object with the changes), but this should solve the problem.
I admiteddly haven't tested it
Did you try with CheckForIllegalCrossThreadCalls ?
Like this :
private void Btn1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
CheckForIllegalCrossThreadCalls = false;
_myViewModel.Name = "Updating from worker thread!"; It causes cross thread issue
CheckForIllegalCrossThreadCalls = true;
});
}
I don't say that is the best way but it's working for me when I work with thread
EDIT :
private void Btn1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
_myViewModel.Invoke((MethodInvoker)(() => _myViewModel.Name = "Updating from worker thread!"; ));
});
}
I have to update the text of a Label. I have bound the Text property of Label to a property and implemented INotifyPropertyChanged event.
My code is as follows:
public partial class MyClass : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _text;
public string ucText
{
get
{
return _text;
}
set
{
_text = value;
NotifyPropertyChanged("ucText");
}
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public MyClass()
{
InitializeComponent();
lblText.DataBindings.Add(new Binding("Text", this, "ucText"));
}
}
In a Button click event in another form, I update the text of the Label as follows:
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 10000; i++)
{
myClass1.ucText = i.ToString();
}
}
Here myClass1 is object of the UserControl posted above.
In the Button click event, the UI hangs when updating the label and then once the loop completes, shows the final value:
9999
Why is my UI not reactive? I have also tried
lblText.DataBindings.Add(new Binding("Text", this, "ucText", false, DataSourceUpdateMode.OnPropertyChanged));
Both forms are running on the same thread, the UI thread. The following scenario is happening:
Button is clicked
Change text to i
Notify UI
Increment i
Go to 2. if i < 10000
Refresh the UI
As long as the loop isn't done, the UI thread won't redraw, as it's still doing some "heavy" work.
You can of course let a new thread handle the "calculation" and let that thread change the value. To start a new thread use either a backgroundworker or start a new thread with the Thread class.
The binding you are using is in fact working.
Edit: Always remember that all calculation that is directly done on the UI thread will block the UI for the time the calculation needs. Always use other threads to do time intensive calculations.
I have a custom control with itemsSource binding:
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
Results.Clear();
foreach (var check in newValue)
{
Results.Add(check as Check);
}
}
protected ObservableCollection<Check> results = new ObservableCollection<Check>();
public ObservableCollection<Check> Results
{
get { return results; }
set { results = value; }
}
Implemented in the main view:
<control:ResultCtrl x:Name="resultCtrl" ItemsSource="{Binding Path=Results, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"></control:ResultCtrl>
Check class:
public class Check : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected string checkStateString;
public string CheckStateString
{
get { return checkStateString; }
set
{
if (value != checkStateString)
{
checkStateString = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("CheckStateString"));
}
}
}
I call a class who calculate checks in the Main View show method:
Thread t = new Thread(new ThreadStart(
delegate
{
Dispatcher.Invoke(DispatcherPriority.Normal, new Action<ResultCtrl>(AddIn.Services.Results.Comprobaciones), resultCtrl);
}
));
t.Start();
In AddIn.Services.Results.Comprobaciones I do:
resultCtrl.ItemsSource = new ObservableCollection<Check>(AddIn.Document.Results);
for every check. Every time I do that I see how ItemsSource change, but Visual only update when the AddIn.Services.Results.Comprobaciones end. I tried to do UpdateLayout() and Items.Refresh() but nothing work.
Any ideas?
This code:
Thread t = new Thread(new ThreadStart(/* ... */);
t.Start();
creates a thread, that is completely useless, because everything it does is a blocking call to UI thread's Dispatcher. In other words, 99.999...% of time it runs on UI thread. You could easily write this:
AddIn.Services.Results.Comprobaciones();
with the same result.
You have to rewrite your code for having any benefits from multi-threading. I have no idea, how does your Comprobaciones method look like, but, obviously, you should call Dispatcher.Invoke only when you need to update something in UI.
Also note, that in most cases you shouldn't create Thread instances directly. Consider using TPL instead (possibly, via async/await, if you're targeting .NET 4.5).